Salome HOME
Improve commentary of methods
[modules/adao.git] / src / daComposant / daCore / Persistence.py
index 3ac7425748f9e8902f1ee9d8ab0598f6f5a2795c..41cc99a5412e5b67cfa5d9a15c036a357c5c5295 100644 (file)
@@ -1,6 +1,6 @@
 #-*-coding:iso-8859-1-*-
 #
-#  Copyright (C) 2008-2010  EDF R&D
+#  Copyright (C) 2008-2011  EDF R&D
 #
 #  This library is free software; you can redistribute it and/or
 #  modify it under the terms of the GNU Lesser General Public
 #
 #  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
 #
+#  Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
+
 __doc__ = """
     Définit des outils de persistence et d'enregistrement de séries de valeurs
     pour analyse ultérieure ou utilisation de calcul.
 """
-__author__ = "Jean-Philippe ARGAUD - Mars 2008"
+__author__ = "Jean-Philippe ARGAUD"
 
 import numpy
 
@@ -57,6 +59,8 @@ class Persistence:
         #
         self.__steps    = []
         self.__values   = []
+        self.__tags     = []
+        self.__tagkeys  = {}
         #
         self.__dynamic  = False
         #
@@ -71,7 +75,7 @@ class Persistence:
         else:
             self.__basetype = basetype
 
-    def store(self, value=None, step=None):
+    def store(self, value=None, step=None, tags={}):
         """
         Stocke une valeur à un pas. Une instanciation est faite avec le type de
         base pour stocker l'objet. Si le pas n'est pas fournit, on utilise
@@ -86,6 +90,9 @@ class Persistence:
         #
         self.__values.append(self.__basetype(value))
         #
+        self.__tags.append(   dict(tags))
+        self.__tagkeys.update(dict(tags))
+        #
         if self.__dynamic: self.__replot()
         for hook, parameters, scheduler in self.__dataobservers:
             if self.__step in scheduler:
@@ -118,41 +125,192 @@ class Persistence:
         return max( self.shape() )
 
     # ---------------------------------------------------------
-    def stepserie(self, item=None, step=None):
+    def itemserie(self, item=None, step=None, tags=None,
+                        allSteps=False):
         """
-        Renvoie par défaut toute la liste des pas de temps. Si l'argument "step"
-        existe dans la liste des pas de stockage effectués, renvoie ce pas
-        "step". Si l'argument "item" est correct, renvoie le pas stockée au
-        numéro "item".
+        Les "item" sont les index de la liste des pas de "step". Ils sont
+        renvoyés par cette fonction selon les filtres définis par les mots-clés.
+        
+        Les comportements sont les suivants :
+            - Renvoie par défaut toute la liste des index.
+            - Si l'argument "item" est valide, renvoie uniquement cet index.
+            - Si l'argument "step" existe dans la liste des pas de stockage,
+              renvoie le premier index (si allSteps=False) ou tous les index
+              (si allSteps=True) de ce "step" dans les pas de stockage.
+            - Si l'argument "tags" est un dictionnaire correct, renvoie les
+              index des pas caractérisés par toutes les paires "tag/valeur" des
+              tags indiqués, ou rien sinon.
+        
+        Cette méthode est à vocation interne pour simplifier les accès aux pas
+        par la méthode "stepserie", aux attributs par la méthode "tagserie" et
+        aux valeurs par la méthode "valueserie".
         """
-        if step is not None and step in self.__steps:
-            return step
-        elif item is not None and item < len(self.__steps):
+        #
+        # Cherche l'item demandé
+        if item is not None and item < len(self.__steps):
+            return [item,]
+        #
+        # Cherche le ou les items dont le "step" est demandé
+        elif step is not None and step in self.__steps:
+            if allSteps:
+                allIndexes = []
+                searchFrom = 0
+                try:
+                    while self.__steps.index(step,searchFrom) >= 0:
+                        searchFrom = self.__steps.index(step,searchFrom)
+                        allIndexes.append( searchFrom )
+                        searchFrom +=1
+                except ValueError, e:
+                    pass
+                return allIndexes
+            else:
+                return [self.__steps.index(step),]
+        #
+        # Cherche le ou les items dont les "tags" sont demandés
+        elif tags is not None and type(tags) is dict :
+            allIndexes = []
+            for i, attributs in enumerate(self.__tags):           # Boucle sur les attributs de chaque pas
+                selection = True                                  # Booleen permettant de traiter la combinaison "ET" des tags
+                for key in tags.keys():                           # Boucle sur tous les tags de filtrage
+                    if key not in self.__tagkeys.keys(): continue # Passe au suivant s'il n'existe nulle part
+                    if not( key in attributs.keys() and attributs[key] == tags[key] ):
+                        selection = False
+                if selection:
+                    allIndexes.append(i)
+            allIndexes = list(set(allIndexes))
+            allIndexes.sort()
+            return allIndexes
+        #
+        # Renvoie par défaut tous les items valides
+        else:
+            return range(len(self.__steps))
+
+    def stepserie(self, item=None, step=None, tags=None):
+        """
+        Les "step" sont les pas nommés de stockage. Par défaut, s'il ne sont pas
+        définis explicitement, ils sont identiques aux index de stockage. Ils
+        sont renvoyés par cette fonction selon les filtres définis par les
+        mots-clés.
+        
+        Les comportements sont les suivants :
+            - Renvoie par défaut toute la liste des pas.
+            - Si l'argument "item" est valide, renvoie le pas à cet index.
+            - Si l'argument "step" existe dans la liste des pas, le renvoie.
+            - Si l'argument "tags" est un dictionnaire correct, renvoie les pas
+              caractérisés par toutes les paires "tag/valeur" des tags indiqués,
+              ou rien sinon.
+        """
+        if item is not None and item < len(self.__steps):
             return self.__steps[item]
+        elif step is not None and step in self.__steps:
+            return step
+        elif tags is not None:
+            allIndexes = self.itemserie(tags = tags)
+            return [self.__steps[index] for index in allIndexes]
         else:
             return self.__steps
 
-    def valueserie(self, item=None, step=None):
+    def valueserie(self, item=None, step=None, tags=None,
+                         allSteps=False):
         """
-        Renvoie par défaut toute la liste des valeurs/objets. Si l'argument
-        "step" existe dans la liste des pas de stockage effectués, renvoie la
-        valeur stockée à ce pas "step". Si l'argument "item" est correct,
-        renvoie la valeur stockée au numéro "item".
+        Les valeurs stockées sont renvoyées par cette fonction selon les filtres
+        définis par les mots-clés.
+        
+        Les comportements sont les suivants :
+            - Renvoie par défaut toute la liste des valeurs.
+            - Si l'argument "item" est valide, renvoie la valeur à cet index.
+            - Si l'argument "step" existe dans la liste des pas de stockage,
+              renvoie la première valeur (si allSteps=False) ou toutes les
+              valeurs (si allSteps=True).
+            - Si l'argument "tags" est un dictionnaire correct, renvoie les
+              valeurs aux pas caractérisés par toutes les paires "tag/valeur"
+              des tags indiqués, ou rien sinon.
         """
-        if step is not None and step in self.__steps:
-            index = self.__steps.index(step)
-            return self.__values[index]
-        elif item is not None and item < len(self.__values):
+        if item is not None and item < len(self.__values):
             return self.__values[item]
+        elif step is not None:
+            allIndexes = self.itemserie(step = step, allSteps = allSteps)
+            if allSteps:
+                return [self.__values[index] for index in allIndexes]
+            else:
+                return self.__values[allIndexes[0]]
+        elif tags is not None:
+            allIndexes = self.itemserie(tags = tags)
+            return [self.__values[index] for index in allIndexes]
         else:
             return self.__values
     
+    def tagserie(self, item=None, step=None, tags=None,
+                       allSteps=False, withValues=False,
+                       outputTag=None):
+        """
+        Les "tag" sont les attributs nommés, sous forme de paires "clé/valeur",
+        qu'il est possible d'associer avec chaque pas de stockage. Par défaut,
+        s'il ne sont pas définis explicitement, il n'y en a pas. Ils sont
+        renvoyés par cette fonction selon les filtres définis par les mots-clés.
+        On obtient uniquement la liste des clés de tags avec "withValues=False"
+        ou la liste des paires "clé/valeurs" avec "withValues=True".
+        
+        On peut aussi obtenir les valeurs d'un tag satisfaisant aux conditions
+        de filtrage en "item/step/tags" en donnant le nom du tag dans
+        "outputTag".
+
+        Les comportements sont les suivants :
+            - Renvoie par défaut toute la liste des tags.
+            - Si l'argument "item" est valide, renvoie le tag à cet index.
+            - Si l'argument "step" existe dans la liste des pas de stockage,
+              renvoie les tags du premier pas (si allSteps=False) ou la liste
+              des tags de tous les pas (si allSteps=True).
+            - Si l'argument "tags" est un dictionnaire correct, renvoie les
+              valeurs aux pas caractérisés par toutes les paires "tag/valeur"
+              des tags indiqués, ou rien sinon.
+        """
+        #
+        # Cherche tous les index satisfaisant les conditions
+        allIndexes = self.itemserie(item = item, step = step, tags = tags, allSteps = allSteps)
+        #
+        # Dans le cas où la sortie donne les valeurs d'un "outputTag"
+        if outputTag is not None and type(outputTag) is str :
+            outputValues = []
+            for index in allIndexes:
+                if outputTag in self.__tags[index].keys():
+                    outputValues.append( self.__tags[index][outputTag] )
+            outputValues = list(set(outputValues))
+            outputValues.sort()
+            return outputValues
+        #
+        # Dans le cas où la sortie donne les tags satisfaisants aux conditions
+        else:
+            if withValues:
+                return [self.__tags[index] for index in allIndexes]
+            else:
+                allTags = {}
+                for index in allIndexes:
+                    allTags.update( self.__tags[index] )
+                allKeys = allTags.keys()
+                allKeys.sort()
+                return allKeys
+
     def stepnumber(self):
         """
         Renvoie le nombre de pas de stockage.
         """
         return len(self.__steps)
 
+    # ---------------------------------------------------------
+    # Méthodes d'accès de type dictionnaire
+    def keys(self):
+        return self.stepserie()
+
+    def values(self):
+        return self.valueserie()
+
+    def items(self):
+        pairs = []
+        for i in xrange(self.stepnumber()):
+            pairs.append( (self.stepserie(item=i), self.valueserie(item=i)) )
+        return pairs
+
     # ---------------------------------------------------------
     def mean(self):
         """
@@ -312,7 +470,7 @@ class Persistence:
         i = -1
         for index in indexes:
             self.__g('set title  "'+str(title).encode('ascii','replace')+' (pas '+str(index)+')"')
-            if ( type(steps) is type([]) ) or ( type(steps) is type(numpy.array([])) ):
+            if ( type(steps) is list ) or ( type(steps) is type(numpy.array([])) ):
                 Steps = list(steps)
             else:
                 Steps = range(len(self.__values[index]))
@@ -471,7 +629,7 @@ class Persistence:
             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
         if ltitle is None:
             ltitle = ""
-        if ( type(steps) is type([]) ) or ( type(steps) is type(numpy.array([])) ):
+        if ( type(steps) is list ) or ( type(steps) is type(numpy.array([])) ):
             Steps = list(steps)
         else:
             Steps = range(len(self.__values[0]))
@@ -502,7 +660,7 @@ class Persistence:
         Scheduler      = None,
         ):
         """
-        Méthode d'association à la variable d'un triplet définissant un observer
+        Association à la variable d'un triplet définissant un observer
         
         Le Scheduler attendu est une fréquence, une simple liste d'index ou un
         xrange des index.
@@ -524,6 +682,32 @@ class Persistence:
         # -----------------------------------------------
         self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
 
+    def removeDataObserver(self,
+        HookFunction   = None,
+        ):
+        """
+        Suppression d'un observer nommé sur la variable.
+        
+        On peut donner dans HookFunction la meme fonction que lors de la
+        définition, ou un simple string qui est le nom de la fonction.
+        
+        """
+        if hasattr(HookFunction,"func_name"):
+            name = str( HookFunction.func_name )
+        elif type(HookFunction) is str:
+            name = str( HookFunction )
+        else:
+            name = None
+        #
+        i = -1
+        index_to_remove = []
+        for [hf, hp, hs] in self.__dataobservers:
+            i = i + 1
+            if name is hf.func_name: index_to_remove.append( i )
+        index_to_remove.reverse()
+        for i in index_to_remove:
+                self.__dataobservers.pop( i )
+
 # ==============================================================================
 class OneScalar(Persistence):
     """
@@ -564,6 +748,201 @@ class OneList(Persistence):
     def __init__(self, name="", unit="", basetype = list):
         Persistence.__init__(self, name, unit, basetype)
 
+def NoType( value ): return value
+
+class OneNoType(Persistence):
+    """
+    Classe définissant le stockage d'un objet sans modification (cast) de type.
+    Attention, selon le véritable type de l'objet stocké à chaque pas, les
+    opérations arithmétiques à base de numpy peuvent être invalides ou donner
+    des résultats inatendus. Cette classe n'est donc à utiliser qu'à bon escient
+    volontairement, et pas du tout par défaut.
+    """
+    def __init__(self, name="", unit="", basetype = NoType):
+        Persistence.__init__(self, name, unit, basetype)
+
+# ==============================================================================
+class CompositePersistence:
+    """
+    Structure de stockage permettant de rassembler plusieurs objets de
+    persistence.
+    
+    Des objets par défaut sont prévus, et des objets supplémentaires peuvent
+    être ajoutés.
+    """
+    def __init__(self, name="", defaults=True):
+        """
+        name : nom courant
+        
+        La gestion interne des données est exclusivement basée sur les variables
+        initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
+        objets comme des attributs) :
+        __StoredObjects : objets de type persistence collectés dans cet objet
+        """
+        self.__name = str(name)
+        #
+        self.__StoredObjects = {}
+        #
+        # Definition des objets par defaut
+        # --------------------------------
+        if defaults:
+            self.__StoredObjects["Informations"]     = OneNoType("Informations")
+            self.__StoredObjects["Background"]       = OneVector("Background", basetype=numpy.array)
+            self.__StoredObjects["BackgroundError"]  = OneMatrix("BackgroundError")
+            self.__StoredObjects["Observation"]      = OneVector("Observation", basetype=numpy.array)
+            self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
+            self.__StoredObjects["Analysis"]         = OneVector("Analysis", basetype=numpy.array)
+            self.__StoredObjects["AnalysisError"]    = OneMatrix("AnalysisError")
+            self.__StoredObjects["Innovation"]       = OneVector("Innovation", basetype=numpy.array)
+            self.__StoredObjects["KalmanGainK"]      = OneMatrix("KalmanGainK")
+            self.__StoredObjects["OperatorH"]        = OneMatrix("OperatorH")
+            self.__StoredObjects["RmsOMA"]           = OneScalar("RmsOMA")
+            self.__StoredObjects["RmsOMB"]           = OneScalar("RmsOMB")
+            self.__StoredObjects["RmsBMA"]           = OneScalar("RmsBMA")
+        #
+
+    def store(self, name=None, value=None, step=None, tags={}):
+        """
+        Stockage d'une valeur "value" pour le "step" dans la variable "name".
+        """
+        if name is None: raise ValueError("Storable object name is required for storage.")
+        if name not in self.__StoredObjects.keys():
+            raise ValueError("No such name '%s' exists in storable objects."%name)
+        self.__StoredObjects[name].store( value=value, step=step, tags=tags )
+
+    def add_object(self, name=None, persistenceType=Persistence, basetype=numpy.array ):
+        """
+        Ajoute dans les objets stockables un nouvel objet défini par son nom, son
+        type de Persistence et son type de base à chaque pas.
+        """
+        if name is None: raise ValueError("Object name is required for adding an object.")
+        if name in self.__StoredObjects.keys():
+            raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
+        self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
+
+    def get_object(self, name=None ):
+        """
+        Renvoie l'objet de type Persistence qui porte le nom demandé.
+        """
+        if name is None: raise ValueError("Object name is required for retrieving an object.")
+        if name not in self.__StoredObjects.keys():
+            raise ValueError("No such name '%s' exists in stored objects."%name)
+        return self.__StoredObjects[name]
+
+    def set_object(self, name=None, objet=None ):
+        """
+        Affecte directement un 'objet' qui porte le nom 'name' demandé.
+        Attention, il n'est pas effectué de vérification sur le type, qui doit
+        comporter les méthodes habituelles de Persistence pour que cela
+        fonctionne.
+        """
+        if name is None: raise ValueError("Object name is required for setting an object.")
+        if name in self.__StoredObjects.keys():
+            raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
+        self.__StoredObjects[name] = objet
+
+    def del_object(self, name=None ):
+        """
+        Supprime un objet de la liste des objets stockables.
+        """
+        if name is None: raise ValueError("Object name is required for retrieving an object.")
+        if name not in self.__StoredObjects.keys():
+            raise ValueError("No such name '%s' exists in stored objects."%name)
+        del self.__StoredObjects[name]
+
+    # ---------------------------------------------------------
+    # Méthodes d'accès de type dictionnaire
+    def __getitem__(self, name=None ):
+        return self.get_object( name )
+
+    def __setitem__(self, name=None, objet=None ):
+        self.set_object( name, objet )
+
+    def keys(self):
+        return self.get_stored_objects(hideVoidObjects = False)
+
+    def values(self):
+        return self.__StoredObjects.values()
+
+    def items(self):
+        return self.__StoredObjects.items()
+
+    # ---------------------------------------------------------
+    def get_stored_objects(self, hideVoidObjects = False):
+        objs = self.__StoredObjects.keys()
+        if hideVoidObjects:
+            usedObjs = []
+            for k in objs:
+                try:
+                    if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
+                except:
+                    pass
+            objs = usedObjs
+        objs.sort()
+        return objs
+
+    # ---------------------------------------------------------
+    def save_composite(self, filename=None, mode="pickle", compress="gzip"):
+        """
+        Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
+        et renvoi le nom du fichier
+        """
+        import os
+        if filename is None:
+            if compress == "gzip":
+                filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
+            elif compress == "bzip2":
+                filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
+            else:
+                filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
+        else:
+            filename = os.path.abspath( filename )
+        #
+        import cPickle
+        if mode == "pickle":
+            if compress == "gzip":
+                import gzip
+                output = gzip.open( filename, 'wb')
+            elif compress == "bzip2":
+                import bz2
+                output = bz2.BZ2File( filename, 'wb')
+            else:
+                output = open( filename, 'wb')
+            cPickle.dump(self, output)
+            output.close()
+        else:
+            raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
+        #
+        return filename
+
+    def load_composite(self, filename=None, mode="pickle", compress="gzip"):
+        """
+        Recharge un objet composite sauvé en fichier
+        """
+        import os
+        if filename is None:
+            raise ValueError("A file name if requested to load a composite.")
+        else:
+            filename = os.path.abspath( filename )
+        #
+        import cPickle
+        if mode == "pickle":
+            if compress == "gzip":
+                import gzip
+                pkl_file = gzip.open( filename, 'rb')
+            elif compress == "bzip2":
+                import bz2
+                pkl_file = bz2.BZ2File( filename, 'rb')
+            else:
+                pkl_file = open(filename, 'rb')
+            output = cPickle.load(pkl_file)
+            for k in output.keys():
+                self[k] = output[k]
+        else:
+            raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
+        #
+        return filename
+
 # ==============================================================================
 if __name__ == "__main__":
     print '\n AUTODIAGNOSTIC \n'
@@ -595,6 +974,26 @@ if __name__ == "__main__":
     del OBJET_DE_TEST
     print
 
+    print "======> Un flottant"
+    OBJET_DE_TEST = OneScalar("My float", unit="cm")
+    OBJET_DE_TEST.store( 5., step="azerty")
+    OBJET_DE_TEST.store(-5., step="poiuyt")
+    OBJET_DE_TEST.store( 1., step="azerty")
+    OBJET_DE_TEST.store( 0., step="xxxxxx")
+    OBJET_DE_TEST.store( 5., step="poiuyt")
+    OBJET_DE_TEST.store(-5., step="azerty")
+    OBJET_DE_TEST.store( 1., step="poiuyt")
+    print "Les pas de stockage :", OBJET_DE_TEST.stepserie()
+    print "Les valeurs         :", OBJET_DE_TEST.valueserie()
+    print "La 2ème valeur      :", OBJET_DE_TEST.valueserie(1)
+    print "La dernière valeur  :", OBJET_DE_TEST.valueserie(-1)
+    print "Premier index       :", OBJET_DE_TEST.valueserie( step = "azerty", allSteps = False )
+    print "Valeurs identiques  :", OBJET_DE_TEST.valueserie( step = "azerty", allSteps = True )
+    print "Premier index       :", OBJET_DE_TEST.valueserie( step = "poiuyt", allSteps = False )
+    print "Valeurs identiques  :", OBJET_DE_TEST.valueserie( step = "poiuyt", allSteps = True )
+    del OBJET_DE_TEST
+    print
+
     print "======> Un entier"
     OBJET_DE_TEST = OneScalar("My int", unit="cm", basetype=int)
     OBJET_DE_TEST.store( 5 )
@@ -704,7 +1103,48 @@ if __name__ == "__main__":
     del OBJET_DE_TEST
     print
 
-    print "======> Affichage d'objets stockés"
+    print "======> Utilisation des méthodes d'accès de type dictionnaire"
+    OBJET_DE_TEST = OneScalar("My int", unit="cm", basetype=int)
+    for i in range(5):
+        OBJET_DE_TEST.store( 7+i )
+    print "Taille \"len\"        :", len(OBJET_DE_TEST)
+    print "Les pas de stockage :", OBJET_DE_TEST.keys()
+    print "Les valeurs         :", OBJET_DE_TEST.values()
+    print "Les paires          :", OBJET_DE_TEST.items()
+    del OBJET_DE_TEST
+    print
+
+    print "======> Persistence composite"
+    OBJET_DE_TEST = CompositePersistence("My CompositePersistence")
+    print "Objets stockables :", OBJET_DE_TEST.get_stored_objects()
+    print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
+    print "--> Stockage d'une valeur de Background"
+    OBJET_DE_TEST.store("Background",numpy.zeros(5))
+    print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
+    print "--> Ajout d'un objet nouveau par defaut, de type vecteur numpy par pas"
+    OBJET_DE_TEST.add_object("ValeursVectorielles")
+    OBJET_DE_TEST.store("ValeursVectorielles",numpy.zeros(5))
+    print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
+    print "--> Ajout d'un objet nouveau de type liste par pas"
+    OBJET_DE_TEST.add_object("ValeursList", persistenceType=OneList )
+    OBJET_DE_TEST.store("ValeursList",range(5))
+    print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
+    print "--> Ajout d'un objet nouveau, de type vecteur string par pas"
+    OBJET_DE_TEST.add_object("ValeursStr", persistenceType=Persistence, basetype=str )
+    OBJET_DE_TEST.store("ValeursStr","IGN3")
+    OBJET_DE_TEST.store("ValeursStr","c021")
+    print "Les valeurs       :", OBJET_DE_TEST.get_object("ValeursStr").valueserie()
+    print "Acces comme dict  :", OBJET_DE_TEST["ValeursStr"].stepserie()
+    print "Acces comme dict  :", OBJET_DE_TEST["ValeursStr"].valueserie()
+    print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
+    print "--> Suppression d'un objet"
+    OBJET_DE_TEST.del_object("ValeursVectorielles")
+    print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
+    print "--> Enregistrement de l'objet complet de Persistence composite"
+    OBJET_DE_TEST.save_composite("composite.pkl", compress="None")
+    print
+
+    print "======> Affichage graphique d'objets stockés"
     OBJET_DE_TEST = Persistence("My object", unit="", basetype=numpy.array)
     D = OBJET_DE_TEST
     vect1 = [1, 2, 1, 2, 1]
@@ -715,7 +1155,7 @@ if __name__ == "__main__":
     D.store(vect1)
     D.store(vect2)
     D.store(vect3)
-    print "Affichage de l'ensemble du stockage sur une même image"
+    print "Affichage graphique de l'ensemble du stockage sur une même image"
     D.stepplot(
         title = "Tous les vecteurs",
         filename="vecteurs.ps",
@@ -724,7 +1164,7 @@ if __name__ == "__main__":
         pause = False )
     print "Stockage d'un quatrième vecteur de longueur différente"
     D.store(vect4)
-    print "Affichage séparé du dernier stockage"
+    print "Affichage graphique séparé du dernier stockage"
     D.plot(
         item  = 3,
         title = "Vecteurs",
@@ -738,7 +1178,7 @@ if __name__ == "__main__":
     del OBJET_DE_TEST
     print
 
-    print "======> Affichage dynamique d'objets"
+    print "======> Affichage graphique dynamique d'objets"
     OBJET_DE_TEST = Persistence("My object", unit="", basetype=float)
     D = OBJET_DE_TEST
     D.plot(
@@ -776,21 +1216,146 @@ if __name__ == "__main__":
         print "  ---> Mise en oeuvre de l'observer"
         print "       var  =",var.valueserie(-1)
         print "       info =",info
+    def obs_bis(var=None,info=None):
+        print "  ---> Mise en oeuvre de l'observer"
+        print "       var  =",var.valueserie(-1)
+        print "       info =",info
     OBJET_DE_TEST = Persistence("My object", unit="", basetype=list)
     D = OBJET_DE_TEST
     D.setDataObserver(
         HookFunction   = obs,
         Scheduler      = [2, 4],
-        HookParameters = "Second observer",
+        HookParameters = "Premier observer",
         )
     D.setDataObserver(
         HookFunction   = obs,
         Scheduler      = xrange(1,3),
+        HookParameters = "Second observer",
+        )
+    D.setDataObserver(
+        HookFunction   = obs_bis,
+        Scheduler      = range(1,3)+range(7,9),
         HookParameters = "Troisième observer",
         )
     for i in range(5):
-        # print
-        print "Action de 2 observers sur la variable observée, étape :",i
+        print "Action de 3 observers sur la variable observée, étape :",i
+        D.store( [i, i, i] )
+    D.removeDataObserver(
+        HookFunction   = obs,
+        )
+    for i in range(5,10):
+        print "Action d'un seul observer sur la variable observée, étape :",i
         D.store( [i, i, i] )
     del OBJET_DE_TEST
     print
+
+    print "======> Utilisation des tags/attributs et stockage puis récupération de l'ensemble"
+    OBJET_DE_TEST = CompositePersistence("My CompositePersistence", defaults=False)
+    OBJET_DE_TEST.add_object("My ecarts", basetype = numpy.array)
+
+    OBJET_DE_TEST.store( "My ecarts", numpy.arange(1,5),   tags = {"Camp":"Base","Carte":"IGN3","Niveau":1024,"Palier":"Premier"} )
+    OBJET_DE_TEST.store( "My ecarts", numpy.arange(1,5)+1, tags = {"Camp":"Base","Carte":"IGN4","Niveau": 210,"Palier":"Premier"} )
+    OBJET_DE_TEST.store( "My ecarts", numpy.arange(1,5)+2, tags = {"Camp":"Base","Carte":"IGN1","Niveau":1024} )
+    OBJET_DE_TEST.store( "My ecarts", numpy.arange(1,5)+3, tags = {"Camp":"Sommet","Carte":"IGN2","Niveau":4024,"Palier":"Second","FullMap":True} )
+
+    print "Les pas de stockage :", OBJET_DE_TEST["My ecarts"].stepserie()
+    print "Les valeurs         :", OBJET_DE_TEST["My ecarts"].valueserie()
+    print "La 2ème valeur      :", OBJET_DE_TEST["My ecarts"].valueserie(1)
+    print "La dernière valeur  :", OBJET_DE_TEST["My ecarts"].valueserie(-1)
+    print "Liste des attributs :", OBJET_DE_TEST["My ecarts"].tagserie()
+    print "Taille \"shape\"      :", OBJET_DE_TEST["My ecarts"].shape()
+    print "Taille \"len\"        :", len(OBJET_DE_TEST["My ecarts"])
+    print
+
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Palier":"Premier"} )
+    print "Valeurs pour tag    :", OBJET_DE_TEST["My ecarts"].valueserie( tags={"Palier":"Premier"} )
+    print
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Carte":"IGN1"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Niveau":1024} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Camp":"Base"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Camp":"TOTO"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Toto":"Premier"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Carte":"IGN1"} )
+    print
+
+    print "Combinaison 'ET' de plusieurs Tags"
+    print "Attendu : [0, 1],    trouvé :",OBJET_DE_TEST["My ecarts"].stepserie( tags={"Camp":"Base", "Palier":"Premier"} )
+    print "Attendu : [],        trouvé :",OBJET_DE_TEST["My ecarts"].stepserie( tags={"Camp":"Sommet", "Palier":"Premier"} )
+    # Attention : {"Camp":"Sommet", "Camp":"Base"} == {"Camp":"Base"}
+    print "Attendu : [0, 1, 2], trouvé :",OBJET_DE_TEST["My ecarts"].stepserie( tags={"Camp":"Sommet", "Camp":"Base"} )
+    print "Attendu : [2],       trouvé :",OBJET_DE_TEST["My ecarts"].stepserie( tags={"Carte":"IGN1", "Niveau":1024} )
+    print
+      
+    print "Liste des tags pour le pas (item) 1  :",OBJET_DE_TEST["My ecarts"].tagserie(item = 1)
+    print "Liste des tags pour le pas (item) 2  :",OBJET_DE_TEST["My ecarts"].tagserie(item = 2)
+    print "Comme le step et l'item sont identiques par défaut, on doit avoir la même chose :"
+    print "Liste des tags pour le pas (step) 1  :",OBJET_DE_TEST["My ecarts"].tagserie(step = 1)
+    print "Liste des tags pour le pas (step) 2  :",OBJET_DE_TEST["My ecarts"].tagserie(step = 2)
+    print
+    print "Liste des tags/valeurs pour le pas 1 :",OBJET_DE_TEST["My ecarts"].tagserie(item = 1, withValues=True)
+    print "Liste des tags/valeurs pour le pas 2 :",OBJET_DE_TEST["My ecarts"].tagserie(item = 2, withValues=True)
+    print
+
+    print "Liste des valeurs possibles pour 1 tag donné 'Camp'   :",OBJET_DE_TEST["My ecarts"].tagserie(outputTag="Camp")
+    print "Liste des valeurs possibles pour 1 tag donné 'Toto'   :",OBJET_DE_TEST["My ecarts"].tagserie(outputTag="Toto")
+    print "Liste des valeurs possibles pour 1 tag donné 'Niveau' :",OBJET_DE_TEST["My ecarts"].tagserie(outputTag="Niveau")
+    print
+
+    OBJET_DE_TEST.add_object("My other ecarts", basetype = numpy.array)
+    OBJET_DE_TEST.store( "My other ecarts", numpy.arange(-1,5),   tags = {"Camp":"Base","Carte":"IGN3","Niveau":1024,"Palier":"Premier"} )
+    OBJET_DE_TEST.store( "My other ecarts", numpy.arange(-1,5)+1, tags = {"Camp":"Base","Carte":"IGN4","Niveau": 210,"Palier":"Premier"} )
+    OBJET_DE_TEST.store( "My other ecarts", numpy.arange(-1,5)+2, tags = {"Camp":"Base","Carte":"IGN1","Niveau":1024} )
+    OBJET_DE_TEST.store( "My other ecarts", numpy.arange(-1,5)+3, tags = {"Camp":"Sommet","Carte":"IGN2","Niveau":4024,"Palier":"Second"} )
+
+    print "Objets présents dans le composite :",OBJET_DE_TEST.get_stored_objects()
+    fichier = "composite.pkl.gz"
+    print "Sauvegarde sur \"%s\"..."%fichier
+    OBJET_DE_TEST.save_composite( fichier )
+    print "Effacement de l'objet en memoire"
+    del OBJET_DE_TEST
+    print
+
+    print "Relecture de l'objet sur \"%s\"..."%fichier
+    OBJET_DE_TEST = CompositePersistence("My CompositePersistence bis", defaults=False)
+    OBJET_DE_TEST.load_composite( fichier )
+    print "Objets présents dans le composite :",OBJET_DE_TEST.get_stored_objects()
+    print "Taille des objets contenus :"
+    for name in OBJET_DE_TEST.get_stored_objects():
+        print "  Objet \"%s\" : taille unitaire de %i"%(name,len(OBJET_DE_TEST[name]))
+
+    print
+    print "Les pas de stockage :", OBJET_DE_TEST["My ecarts"].stepserie()
+    print "Les valeurs         :", OBJET_DE_TEST["My ecarts"].valueserie()
+    print "La 2ème valeur      :", OBJET_DE_TEST["My ecarts"].valueserie(1)
+    print "La dernière valeur  :", OBJET_DE_TEST["My ecarts"].valueserie(-1)
+    print "Liste des attributs :", OBJET_DE_TEST["My ecarts"].tagserie()
+    print "Taille \"shape\"      :", OBJET_DE_TEST["My ecarts"].shape()
+    print "Taille \"len\"        :", len(OBJET_DE_TEST["My ecarts"])
+    print
+
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Palier":"Premier"} )
+    print "Valeurs pour tag    :", OBJET_DE_TEST["My ecarts"].valueserie( tags={"Palier":"Premier"} )
+    print
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Carte":"IGN1"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Niveau":1024} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Camp":"Base"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Camp":"TOTO"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Toto":"Premier"} )
+    print "Pas pour tag        :", OBJET_DE_TEST["My ecarts"].stepserie( tags={"Carte":"IGN1"} )
+    print
+    print "Attributs                 :", OBJET_DE_TEST["My ecarts"].tagserie()
+    print "Attributs pour tag filtré :", OBJET_DE_TEST["My ecarts"].tagserie( tags={"Camp":"Base"} )
+    print "Attributs pour tag filtré :", OBJET_DE_TEST["My ecarts"].tagserie( tags={"Niveau":4024} )
+    print
+    print "Attributs et valeurs                 :", OBJET_DE_TEST["My ecarts"].tagserie( withValues=True )
+    print "Attributs et valeurs pour tag filtré :", OBJET_DE_TEST["My ecarts"].tagserie( withValues=True, tags={"Camp":"Base"} )
+    print "Attributs et valeurs pour tag filtré :", OBJET_DE_TEST["My ecarts"].tagserie( withValues=True, tags={"Niveau":4024} )
+    print
+    print "Valeur d'attribut pour un tag donné 'BU'           :", OBJET_DE_TEST["My ecarts"].tagserie( outputTag = "Niveau" )
+    print "Valeur d'attribut pour un tag donné 'BU' filtré    :", OBJET_DE_TEST["My ecarts"].tagserie( outputTag = "Niveau", tags={"Camp":"Base"} )
+    print "Valeur d'attribut pour un tag donné 'BU' filtré    :", OBJET_DE_TEST["My ecarts"].tagserie( outputTag = "Niveau", tags={"Palier":"Second"} )
+    print "Valeur d'attribut pour un tag donné 'Camp' filtré  :", OBJET_DE_TEST["My ecarts"].tagserie( outputTag = "Camp", tags={"Palier":"Premier"} )
+    print "Valeur d'attribut pour un tag donné 'Carte' filtré :", OBJET_DE_TEST["My ecarts"].tagserie( outputTag = "Carte", tags={"Palier":"Premier"} )
+    print "Valeur d'attribut pour un tag donné 'Carte' filtré :", OBJET_DE_TEST["My ecarts"].tagserie( outputTag = "Carte", tags={"Palier":"Premier","Niveau":4024} )
+    print "Valeur d'attribut pour un tag donné 'Carte' filtré :", OBJET_DE_TEST["My ecarts"].tagserie( outputTag = "Carte", tags={"Palier":"Premier","Niveau":210} )
+    print