Salome HOME
b8d100d142f7376725f66fc4a7f97f904397c63e
[modules/adao.git] / src / daComposant / daCore / Persistence.py
1 #-*-coding:iso-8859-1-*-
2 #
3 #  Copyright (C) 2008-2010  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 __doc__ = """
22     Définit des outils de persistence et d'enregistrement de séries de valeurs
23     pour analyse ultérieure ou utilisation de calcul.
24 """
25 __author__ = "Jean-Philippe ARGAUD - Mars 2008"
26
27 import numpy
28
29 from PlatformInfo import PathManagement ; PathManagement()
30
31 # ==============================================================================
32 class Persistence:
33     """
34     Classe générale de persistence définissant les accesseurs nécessaires
35     (Template)
36     """
37     def __init__(self, name="", unit="", basetype=str):
38         """
39         name : nom courant
40         unit : unité
41         basetype : type de base de l'objet stocké à chaque pas
42         
43         La gestion interne des données est exclusivement basée sur les variables
44         initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
45         objets comme des attributs) :
46         __step   : numérotation par défaut du pas courant
47         __basetype : le type de base de chaque valeur, sous la forme d'un type
48                      permettant l'instanciation ou le casting Python 
49         __steps  : les pas de stockage. Par défaut, c'est __step
50         __values : les valeurs de stockage. Par défaut, c'est None
51         """
52         self.__name = str(name)
53         self.__unit = str(unit)
54         #
55         self.__step     = -1
56         self.__basetype = basetype
57         #
58         self.__steps    = []
59         self.__values   = []
60         #
61         self.__dynamic  = False
62         #
63         self.__dataobservers = []
64     
65     def basetype(self, basetype=None):
66         """
67         Renvoie ou met en place le type de base des objets stockés
68         """
69         if basetype is None:
70             return self.__basetype
71         else:
72             self.__basetype = basetype
73
74     def store(self, value=None, step=None):
75         """
76         Stocke une valeur à un pas. Une instanciation est faite avec le type de
77         base pour stocker l'objet. Si le pas n'est pas fournit, on utilise
78         l'étape de stockage comme valeur de pas.
79         """
80         if value is None: raise ValueError("Value argument required")
81         self.__step += 1
82         if step is not None:
83             self.__steps.append(step)
84         else:
85             self.__steps.append(self.__step)
86         #
87         self.__values.append(self.__basetype(value))
88         #
89         if self.__dynamic: self.__replot()
90         for hook, parameters, scheduler in self.__dataobservers:
91             if self.__step in scheduler:
92                 hook( self, parameters )
93
94     def shape(self):
95         """
96         Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un
97         objet numpy, renvoie le shape. Si c'est un entier, un flottant, un
98         complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la
99         longueur. Par défaut, renvoie 1.
100         """
101         if len(self.__values) > 0:
102             if self.__basetype in [numpy.matrix, numpy.array]:
103                 return self.__values[-1].shape
104             elif self.__basetype in [int, float]:
105                 return (1,)
106             elif self.__basetype in [list, dict]:
107                 return (len(self.__values[-1]),)
108             else:
109                 return (1,)
110         else:
111             raise ValueError("Object has no shape before its first storage")
112
113     def __len__(self):
114         """
115         Renvoie le nombre d'éléments dans un séquence ou la plus grande
116         dimension d'une matrice
117         """
118         return max( self.shape() )
119
120     # ---------------------------------------------------------
121     def stepserie(self, item=None, step=None):
122         """
123         Renvoie par défaut toute la liste des pas de temps. Si l'argument "step"
124         existe dans la liste des pas de stockage effectués, renvoie ce pas
125         "step". Si l'argument "item" est correct, renvoie le pas stockée au
126         numéro "item".
127         """
128         if step is not None and step in self.__steps:
129             return step
130         elif item is not None and item < len(self.__steps):
131             return self.__steps[item]
132         else:
133             return self.__steps
134
135     def valueserie(self, item=None, step=None):
136         """
137         Renvoie par défaut toute la liste des valeurs/objets. Si l'argument
138         "step" existe dans la liste des pas de stockage effectués, renvoie la
139         valeur stockée à ce pas "step". Si l'argument "item" est correct,
140         renvoie la valeur stockée au numéro "item".
141         """
142         if step is not None and step in self.__steps:
143             index = self.__steps.index(step)
144             return self.__values[index]
145         elif item is not None and item < len(self.__values):
146             return self.__values[item]
147         else:
148             return self.__values
149     
150     def stepnumber(self):
151         """
152         Renvoie le nombre de pas de stockage.
153         """
154         return len(self.__steps)
155
156     # ---------------------------------------------------------
157     # Méthodes d'accès de type dictionnaire
158     def keys(self):
159         return self.stepserie()
160
161     def values(self):
162         return self.valueserie()
163
164     def items(self):
165         pairs = []
166         for i in xrange(self.stepnumber()):
167             pairs.append( (self.stepserie(item=i), self.valueserie(item=i)) )
168         return pairs
169
170     # ---------------------------------------------------------
171     def mean(self):
172         """
173         Renvoie la valeur moyenne des données à chaque pas. Il faut que le type
174         de base soit compatible avec les types élémentaires numpy.
175         """
176         try:
177             return [numpy.matrix(item).mean() for item in self.__values]
178         except:
179             raise TypeError("Base type is incompatible with numpy")
180
181     def std(self, ddof=0):
182         """
183         Renvoie l'écart-type des données à chaque pas. Il faut que le type de
184         base soit compatible avec les types élémentaires numpy.
185         
186         ddof : c'est le nombre de degrés de liberté pour le calcul de
187                l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
188         """
189         try:
190             if numpy.version.version >= '1.1.0':
191                 return [numpy.matrix(item).std(ddof=ddof) for item in self.__values]
192             else:
193                 return [numpy.matrix(item).std() for item in self.__values]
194         except:
195             raise TypeError("Base type is incompatible with numpy")
196
197     def sum(self):
198         """
199         Renvoie la somme des données à chaque pas. Il faut que le type de
200         base soit compatible avec les types élémentaires numpy.
201         """
202         try:
203             return [numpy.matrix(item).sum() for item in self.__values]
204         except:
205             raise TypeError("Base type is incompatible with numpy")
206
207     def min(self):
208         """
209         Renvoie le minimum des données à chaque pas. Il faut que le type de
210         base soit compatible avec les types élémentaires numpy.
211         """
212         try:
213             return [numpy.matrix(item).min() for item in self.__values]
214         except:
215             raise TypeError("Base type is incompatible with numpy")
216
217     def max(self):
218         """
219         Renvoie le maximum des données à chaque pas. Il faut que le type de
220         base soit compatible avec les types élémentaires numpy.
221         """
222         try:
223             return [numpy.matrix(item).max() for item in self.__values]
224         except:
225             raise TypeError("Base type is incompatible with numpy")
226
227     def __preplot(self,
228             title    = "",
229             xlabel   = "",
230             ylabel   = "",
231             ltitle   = None,
232             geometry = "600x400",
233             persist  = False,
234             pause    = True,
235             ):
236         import os
237         #
238         # Vérification de la disponibilité du module Gnuplot
239         try:
240             import Gnuplot
241             self.__gnuplot = Gnuplot
242         except:
243             raise ImportError("The Gnuplot module is required to plot the object.")
244         #
245         # Vérification et compléments sur les paramètres d'entrée
246         if persist:
247             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
248         else:
249             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
250         if ltitle is None:
251             ltitle = ""
252         self.__g = self.__gnuplot.Gnuplot() # persist=1
253         self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term)
254         self.__g('set style data lines')
255         self.__g('set grid')
256         self.__g('set autoscale')
257         self.__g('set xlabel "'+str(xlabel).encode('ascii','replace')+'"')
258         self.__g('set ylabel "'+str(ylabel).encode('ascii','replace')+'"')
259         self.__title  = title
260         self.__ltitle = ltitle
261         self.__pause  = pause
262
263     def plot(self, item=None, step=None,
264             steps    = None,
265             title    = "",
266             xlabel   = "",
267             ylabel   = "",
268             ltitle   = None,
269             geometry = "600x400",
270             filename = "",
271             dynamic  = False,
272             persist  = False,
273             pause    = True,
274             ):
275         """
276         Renvoie un affichage de la valeur à chaque pas, si elle est compatible
277         avec un affichage Gnuplot (donc essentiellement un vecteur). Si
278         l'argument "step" existe dans la liste des pas de stockage effectués,
279         renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
280         "item" est correct, renvoie l'affichage de la valeur stockée au numéro
281         "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
282         affichage successif de tous les pas.
283
284         Arguments :
285             - step     : valeur du pas à afficher
286             - item     : index de la valeur à afficher
287             - steps    : liste unique des pas de l'axe des X, ou None si c'est
288                          la numérotation par défaut
289             - title    : base du titre général, qui sera automatiquement
290                          complétée par la mention du pas
291             - xlabel   : label de l'axe des X
292             - ylabel   : label de l'axe des Y
293             - ltitle   : titre associé au vecteur tracé
294             - geometry : taille en pixels de la fenêtre et position du coin haut
295                          gauche, au format X11 : LxH+X+Y (défaut : 600x400)
296             - filename : base de nom de fichier Postscript pour une sauvegarde,
297                          qui est automatiquement complétée par le numéro du
298                          fichier calculé par incrément simple de compteur
299             - dynamic  : effectue un affichage des valeurs à chaque stockage
300                          (au-delà du second) La méthode "plot" permet de déclarer
301                          l'affichage dynamique, et c'est la méthode "__replot"
302                          qui est utilisée pour l'effectuer
303             - persist  : booléen indiquant que la fenêtre affichée sera
304                          conservée lors du passage au dessin suivant
305                          Par défaut, persist = False
306             - pause    : booléen indiquant une pause après chaque tracé, et
307                          attendant un Return
308                          Par défaut, pause = True
309         """
310         import os
311         if not self.__dynamic:
312             self.__preplot(title, xlabel, ylabel, ltitle, geometry, persist, pause )
313             if dynamic:
314                 self.__dynamic = True
315                 if len(self.__values) == 0: return 0
316         #
317         # Tracé du ou des vecteurs demandés
318         indexes = []
319         if step is not None and step in self.__steps:
320             indexes.append(self.__steps.index(step))
321         elif item is not None and item < len(self.__values):
322             indexes.append(item)
323         else:
324             indexes = indexes + range(len(self.__values))
325         #
326         i = -1
327         for index in indexes:
328             self.__g('set title  "'+str(title).encode('ascii','replace')+' (pas '+str(index)+')"')
329             if ( type(steps) is type([]) ) or ( type(steps) is type(numpy.array([])) ):
330                 Steps = list(steps)
331             else:
332                 Steps = range(len(self.__values[index]))
333             #
334             self.__g.plot( self.__gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
335             #
336             if filename != "":
337                 i += 1
338                 stepfilename = "%s_%03i.ps"%(filename,i)
339                 if os.path.isfile(stepfilename):
340                     raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
341                 self.__g.hardcopy(filename=stepfilename, color=1)
342             if self.__pause:
343                 raw_input('Please press return to continue...\n')
344
345     def __replot(self):
346         """
347         Affichage dans le cas du suivi dynamique de la variable
348         """
349         if self.__dynamic and len(self.__values) < 2: return 0
350         #
351         import os
352         self.__g('set title  "'+str(self.__title).encode('ascii','replace'))
353         Steps = range(len(self.__values))
354         self.__g.plot( self.__gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
355         #
356         if self.__pause:
357             raw_input('Please press return to continue...\n')
358
359     # ---------------------------------------------------------
360     def stepmean(self):
361         """
362         Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
363         longueur des pas. Il faut que le type de base soit compatible avec
364         les types élémentaires numpy.
365         """
366         try:
367             return numpy.matrix(self.__values).mean()
368         except:
369             raise TypeError("Base type is incompatible with numpy")
370
371     def stepstd(self, ddof=0):
372         """
373         Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
374         longueur des pas. Il faut que le type de base soit compatible avec
375         les types élémentaires numpy.
376         
377         ddof : c'est le nombre de degrés de liberté pour le calcul de
378                l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
379         """
380         try:
381             if numpy.version.version >= '1.1.0':
382                 return numpy.matrix(self.__values).std(ddof=ddof)
383             else:
384                 return numpy.matrix(self.__values).std()
385         except:
386             raise TypeError("Base type is incompatible with numpy")
387
388     def stepsum(self):
389         """
390         Renvoie la somme de toutes les valeurs sans tenir compte de la
391         longueur des pas. Il faut que le type de base soit compatible avec
392         les types élémentaires numpy.
393         """
394         try:
395             return numpy.matrix(self.__values).sum()
396         except:
397             raise TypeError("Base type is incompatible with numpy")
398
399     def stepmin(self):
400         """
401         Renvoie le minimum de toutes les valeurs sans tenir compte de la
402         longueur des pas. Il faut que le type de base soit compatible avec
403         les types élémentaires numpy.
404         """
405         try:
406             return numpy.matrix(self.__values).min()
407         except:
408             raise TypeError("Base type is incompatible with numpy")
409
410     def stepmax(self):
411         """
412         Renvoie le maximum de toutes les valeurs sans tenir compte de la
413         longueur des pas. Il faut que le type de base soit compatible avec
414         les types élémentaires numpy.
415         """
416         try:
417             return numpy.matrix(self.__values).max()
418         except:
419             raise TypeError("Base type is incompatible with numpy")
420
421     def cumsum(self):
422         """
423         Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
424         longueur des pas. Il faut que le type de base soit compatible avec
425         les types élémentaires numpy.
426         """
427         try:
428             return numpy.matrix(self.__values).cumsum(axis=0)
429         except:
430             raise TypeError("Base type is incompatible with numpy")
431
432     # On pourrait aussi utiliser les autres attributs d'une "matrix", comme
433     # "tofile", "min"...
434
435     def stepplot(self,
436             steps    = None,
437             title    = "",
438             xlabel   = "",
439             ylabel   = "",
440             ltitle   = None,
441             geometry = "600x400",
442             filename = "",
443             persist  = False,
444             pause    = True,
445             ):
446         """
447         Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
448         elles sont compatibles avec un affichage Gnuplot (donc essentiellement
449         un vecteur). Si l'argument "step" existe dans la liste des pas de
450         stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
451         "step". Si l'argument "item" est correct, renvoie l'affichage de la
452         valeur stockée au numéro "item".
453
454         Arguments :
455             - steps    : liste unique des pas de l'axe des X, ou None si c'est
456                          la numérotation par défaut
457             - title    : base du titre général, qui sera automatiquement
458                          complétée par la mention du pas
459             - xlabel   : label de l'axe des X
460             - ylabel   : label de l'axe des Y
461             - ltitle   : titre associé au vecteur tracé
462             - geometry : taille en pixels de la fenêtre et position du coin haut
463                          gauche, au format X11 : LxH+X+Y (défaut : 600x400)
464             - filename : nom de fichier Postscript pour une sauvegarde
465             - persist  : booléen indiquant que la fenêtre affichée sera
466                          conservée lors du passage au dessin suivant
467                          Par défaut, persist = False
468             - pause    : booléen indiquant une pause après chaque tracé, et
469                          attendant un Return
470                          Par défaut, pause = True
471         """
472         import os
473         #
474         # Vérification de la disponibilité du module Gnuplot
475         try:
476             import Gnuplot
477             self.__gnuplot = Gnuplot
478         except:
479             raise ImportError("The Gnuplot module is required to plot the object.")
480         #
481         # Vérification et compléments sur les paramètres d'entrée
482         if persist:
483             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
484         else:
485             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
486         if ltitle is None:
487             ltitle = ""
488         if ( type(steps) is type([]) ) or ( type(steps) is type(numpy.array([])) ):
489             Steps = list(steps)
490         else:
491             Steps = range(len(self.__values[0]))
492         self.__g = self.__gnuplot.Gnuplot() # persist=1
493         self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term)
494         self.__g('set style data lines')
495         self.__g('set grid')
496         self.__g('set autoscale')
497         self.__g('set title  "'+str(title).encode('ascii','replace') +'"')
498         self.__g('set xlabel "'+str(xlabel).encode('ascii','replace')+'"')
499         self.__g('set ylabel "'+str(ylabel).encode('ascii','replace')+'"')
500         #
501         # Tracé du ou des vecteurs demandés
502         indexes = range(len(self.__values))
503         self.__g.plot( self.__gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle+" (pas 0)" ) )
504         for index in indexes:
505             self.__g.replot( self.__gnuplot.Data( Steps, self.__values[index], title=ltitle+" (pas %i)"%index ) )
506         #
507         if filename != "":
508             self.__g.hardcopy(filename=filename, color=1)
509         if pause:
510             raw_input('Please press return to continue...\n')
511
512     # ---------------------------------------------------------
513     def setDataObserver(self,
514         HookFunction   = None,
515         HookParameters = None,
516         Scheduler      = None,
517         ):
518         """
519         Méthode d'association à la variable d'un triplet définissant un observer
520         
521         Le Scheduler attendu est une fréquence, une simple liste d'index ou un
522         xrange des index.
523         """
524         #
525         # Vérification du Scheduler
526         # -------------------------
527         maxiter = int( 1e9 )
528         if type(Scheduler) is int:    # Considéré comme une fréquence à partir de 0
529             Schedulers = xrange( 0, maxiter, int(Scheduler) )
530         elif type(Scheduler) is xrange: # Considéré comme un itérateur
531             Schedulers = Scheduler
532         elif type(Scheduler) is list: # Considéré comme des index explicites
533             Schedulers = map( long, Scheduler )
534         else:                         # Dans tous les autres cas, activé par défaut
535             Schedulers = xrange( 0, maxiter )
536         #
537         # Stockage interne de l'observer dans la variable
538         # -----------------------------------------------
539         self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
540
541     def removeDataObserver(self,
542         HookFunction   = None,
543         ):
544         """
545         Méthode de suppression d'un observer sur la variable.
546         
547         On peut donner dans HookFunction la meme fonction que lors de la
548         définition, ou un simple string qui est le nom de la fonction.
549         
550         """
551         if hasattr(HookFunction,"func_name"):
552             name = str( HookFunction.func_name )
553         elif type(HookFunction) is str:
554             name = str( HookFunction )
555         else:
556             name = None
557         #
558         i = -1
559         index_to_remove = []
560         for [hf, hp, hs] in self.__dataobservers:
561             i = i + 1
562             if name is hf.func_name: index_to_remove.append( i )
563         index_to_remove.reverse()
564         for i in index_to_remove:
565                 self.__dataobservers.pop( i )
566
567 # ==============================================================================
568 class OneScalar(Persistence):
569     """
570     Classe définissant le stockage d'une valeur unique réelle (float) par pas
571     
572     Le type de base peut être changé par la méthode "basetype", mais il faut que
573     le nouveau type de base soit compatible avec les types par éléments de 
574     numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
575     ou des matrices comme dans les classes suivantes, mais c'est déconseillé
576     pour conserver une signification claire des noms.
577     """
578     def __init__(self, name="", unit="", basetype = float):
579         Persistence.__init__(self, name, unit, basetype)
580
581 class OneVector(Persistence):
582     """
583     Classe définissant le stockage d'une liste (list) de valeurs homogènes par
584     hypothèse par pas. Pour éviter les confusions, ne pas utiliser la classe
585     "OneVector" pour des données hétérogènes, mais bien "OneList".
586     """
587     def __init__(self, name="", unit="", basetype = list):
588         Persistence.__init__(self, name, unit, basetype)
589
590 class OneMatrix(Persistence):
591     """
592     Classe définissant le stockage d'une matrice de valeurs (numpy.matrix) par
593     pas
594     """
595     def __init__(self, name="", unit="", basetype = numpy.matrix):
596         Persistence.__init__(self, name, unit, basetype)
597
598 class OneList(Persistence):
599     """
600     Classe définissant le stockage d'une liste de valeurs potentiellement
601     hétérogènes (list) par pas. Pour éviter les confusions, ne pas utiliser la
602     classe "OneVector" pour des données hétérogènes, mais bien "OneList".
603     """
604     def __init__(self, name="", unit="", basetype = list):
605         Persistence.__init__(self, name, unit, basetype)
606
607 def NoType( value ): return value
608
609 class OneNoType(Persistence):
610     """
611     Classe définissant le stockage d'un objet sans modification (cast) de type.
612     Attention, selon le véritable type de l'objet stocké à chaque pas, les
613     opérations arithmétiques à base de numpy peuvent être invalides ou donner
614     des résultats inatendus. Cette classe n'est donc à utiliser qu'à bon escient
615     volontairement, et pas du tout par défaut.
616     """
617     def __init__(self, name="", unit="", basetype = NoType):
618         Persistence.__init__(self, name, unit, basetype)
619
620 # ==============================================================================
621 class CompositePersistence:
622     """
623     Structure de stockage permettant de rassembler plusieurs objets de
624     persistence.
625     
626     Des objets par défaut sont prévus, et des objets supplémentaires peuvent
627     être ajoutés.
628     """
629     def __init__(self, name=""):
630         """
631         name : nom courant
632         
633         La gestion interne des données est exclusivement basée sur les variables
634         initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
635         objets comme des attributs) :
636         __StoredObjects : objets de type persistence collectés dans cet objet
637         """
638         self.__name = str(name)
639         #
640         self.__StoredObjects = {}
641         #
642         # Definition des objets par defaut
643         # --------------------------------
644         self.__StoredObjects["Background"]       = OneVector("Background", basetype=numpy.array)
645         self.__StoredObjects["BackgroundError"]  = OneMatrix("BackgroundError")
646         self.__StoredObjects["Observation"]      = OneVector("Observation", basetype=numpy.array)
647         self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
648         self.__StoredObjects["Analysis"]         = OneVector("Analysis", basetype=numpy.array)
649         self.__StoredObjects["AnalysisError"]    = OneMatrix("AnalysisError")
650         self.__StoredObjects["Innovation"]       = OneVector("Innovation", basetype=numpy.array)
651         self.__StoredObjects["KalmanGainK"]      = OneMatrix("KalmanGainK")
652         self.__StoredObjects["OperatorH"]        = OneMatrix("OperatorH")
653         self.__StoredObjects["RmsOMA"]           = OneScalar("RmsOMA")
654         self.__StoredObjects["RmsOMB"]           = OneScalar("RmsOMB")
655         self.__StoredObjects["RmsBMA"]           = OneScalar("RmsBMA")
656         #
657
658     def store(self, name=None, value=None, step=None):
659         """
660         Stockage d'une valeur "value" pour le "step" dans la variable "name".
661         """
662         if name is None: raise ValueError("Storable object name is required for storage.")
663         if name not in self.__StoredObjects.keys():
664             raise ValueError("No such name '%s' exists in storable objects."%name)
665         self.__StoredObjects[name].store( value=value, step=step )
666
667     def add_object(self, name=None, persistenceType=Persistence, basetype=numpy.array ):
668         """
669         Ajoute dans les objets stockables un nouvel objet défini par son nom, son
670         type de Persistence et son type de base à chaque pas.
671         """
672         if name is None: raise ValueError("Object name is required for adding an object.")
673         if name in self.__StoredObjects.keys():
674             raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
675         self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
676
677     def get_object(self, name=None ):
678         """
679         Renvoie l'objet de type Persistence qui porte le nom demandé.
680         """
681         if name is None: raise ValueError("Object name is required for retrieving an object.")
682         if name not in self.__StoredObjects.keys():
683             raise ValueError("No such name '%s' exists in stored objects."%name)
684         return self.__StoredObjects[name]
685
686     def set_object(self, name=None, objet=None ):
687         """
688         Affecte directement un 'objet' qui porte le nom 'name' demandé.
689         Attention, il n'est pas effectué de vérification sur le type, qui doit
690         comporter les méthodes habituelles de Persistence pour que cela
691         fonctionne.
692         """
693         if name is None: raise ValueError("Object name is required for setting an object.")
694         if name in self.__StoredObjects.keys():
695             raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
696         self.__StoredObjects[name] = objet
697
698     def del_object(self, name=None ):
699         """
700         Supprime un objet de la liste des objets stockables.
701         """
702         if name is None: raise ValueError("Object name is required for retrieving an object.")
703         if name not in self.__StoredObjects.keys():
704             raise ValueError("No such name '%s' exists in stored objects."%name)
705         del self.__StoredObjects[name]
706
707     # ---------------------------------------------------------
708     # Méthodes d'accès de type dictionnaire
709     def __getitem__(self, name=None ):
710         return self.get_object( name )
711
712     def __setitem__(self, name=None, objet=None ):
713         self.set_object( name, objet )
714
715     def keys(self):
716         return self.get_stored_objects(hideVoidObjects = False)
717
718     def values(self):
719         return self.__StoredObjects.values()
720
721     def items(self):
722         return self.__StoredObjects.items()
723
724     # ---------------------------------------------------------
725     def get_stored_objects(self, hideVoidObjects = False):
726         objs = self.__StoredObjects.keys()
727         if hideVoidObjects:
728             usedObjs = []
729             for k in objs:
730                 try:
731                     if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
732                 except:
733                     pass
734             objs = usedObjs
735         objs.sort()
736         return objs
737
738     # ---------------------------------------------------------
739     def save_composite(self, filename=None, mode="pickle"):
740         """
741         Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
742         et renvoi le nom du fichier
743         """
744         import os
745         if filename is None:
746             filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
747         else:
748             filename = os.path.abspath( filename )
749         #
750         import cPickle
751         if mode is "pickle":
752             output = open( filename, 'wb')
753             cPickle.dump(self, output)
754             output.close()
755         else:
756             raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
757         #
758         return filename
759
760     def load_composite(self, filename=None, mode="pickle"):
761         """
762         Recharge un objet composite sauvé en fichier
763         """
764         import os
765         if filename is None:
766             raise ValueError("A file name if requested to load a composite.")
767         else:
768             filename = os.path.abspath( filename )
769         #
770         import cPickle
771         if mode is "pickle":
772             pkl_file = open(filename, 'rb')
773             output = cPickle.load(pkl_file)
774             for k in output.keys():
775                 self[k] = output[k]
776         else:
777             raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
778         #
779         return filename
780
781 # ==============================================================================
782 if __name__ == "__main__":
783     print '\n AUTODIAGNOSTIC \n'
784
785     print "======> Un flottant"
786     OBJET_DE_TEST = OneScalar("My float", unit="cm")
787     OBJET_DE_TEST.store( 5.)
788     OBJET_DE_TEST.store(-5.)
789     OBJET_DE_TEST.store( 1.)
790     print "Les pas de stockage :", OBJET_DE_TEST.stepserie()
791     print "Les valeurs         :", OBJET_DE_TEST.valueserie()
792     print "La 2ème valeur      :", OBJET_DE_TEST.valueserie(1)
793     print "La dernière valeur  :", OBJET_DE_TEST.valueserie(-1)
794     print "Valeurs par pas :"
795     print "  La moyenne        :", OBJET_DE_TEST.mean()
796     print "  L'écart-type      :", OBJET_DE_TEST.std()
797     print "  La somme          :", OBJET_DE_TEST.sum()
798     print "  Le minimum        :", OBJET_DE_TEST.min()
799     print "  Le maximum        :", OBJET_DE_TEST.max()
800     print "Valeurs globales :"
801     print "  La moyenne        :", OBJET_DE_TEST.stepmean()
802     print "  L'écart-type      :", OBJET_DE_TEST.stepstd()
803     print "  La somme          :", OBJET_DE_TEST.stepsum()
804     print "  Le minimum        :", OBJET_DE_TEST.stepmin()
805     print "  Le maximum        :", OBJET_DE_TEST.stepmax()
806     print "  La somme cumulée  :", OBJET_DE_TEST.cumsum()
807     print "Taille \"shape\"      :", OBJET_DE_TEST.shape()
808     print "Taille \"len\"        :", len(OBJET_DE_TEST)
809     del OBJET_DE_TEST
810     print
811
812     print "======> Un entier"
813     OBJET_DE_TEST = OneScalar("My int", unit="cm", basetype=int)
814     OBJET_DE_TEST.store( 5 )
815     OBJET_DE_TEST.store(-5 )
816     OBJET_DE_TEST.store( 1.)
817     print "Les pas de stockage :", OBJET_DE_TEST.stepserie()
818     print "Les valeurs         :", OBJET_DE_TEST.valueserie()
819     print "La 2ème valeur      :", OBJET_DE_TEST.valueserie(1)
820     print "La dernière valeur  :", OBJET_DE_TEST.valueserie(-1)
821     print "Valeurs par pas :"
822     print "  La moyenne        :", OBJET_DE_TEST.mean()
823     print "  L'écart-type      :", OBJET_DE_TEST.std()
824     print "  La somme          :", OBJET_DE_TEST.sum()
825     print "  Le minimum        :", OBJET_DE_TEST.min()
826     print "  Le maximum        :", OBJET_DE_TEST.max()
827     print "Valeurs globales :"
828     print "  La moyenne        :", OBJET_DE_TEST.stepmean()
829     print "  L'écart-type      :", OBJET_DE_TEST.stepstd()
830     print "  La somme          :", OBJET_DE_TEST.stepsum()
831     print "  Le minimum        :", OBJET_DE_TEST.stepmin()
832     print "  Le maximum        :", OBJET_DE_TEST.stepmax()
833     print "  La somme cumulée  :", OBJET_DE_TEST.cumsum()
834     print "Taille \"shape\"      :", OBJET_DE_TEST.shape()
835     print "Taille \"len\"        :", len(OBJET_DE_TEST)
836     del OBJET_DE_TEST
837     print
838
839     print "======> Un booléen"
840     OBJET_DE_TEST = OneScalar("My bool", unit="", basetype=bool)
841     OBJET_DE_TEST.store( True  )
842     OBJET_DE_TEST.store( False )
843     OBJET_DE_TEST.store( True  )
844     print "Les pas de stockage :", OBJET_DE_TEST.stepserie()
845     print "Les valeurs         :", OBJET_DE_TEST.valueserie()
846     print "La 2ème valeur      :", OBJET_DE_TEST.valueserie(1)
847     print "La dernière valeur  :", OBJET_DE_TEST.valueserie(-1)
848     print "Taille \"shape\"      :", OBJET_DE_TEST.shape()
849     print "Taille \"len\"        :", len(OBJET_DE_TEST)
850     del OBJET_DE_TEST
851     print
852
853     print "======> Un vecteur de flottants"
854     OBJET_DE_TEST = OneVector("My float vector", unit="cm")
855     OBJET_DE_TEST.store( (5 , -5) )
856     OBJET_DE_TEST.store( (-5, 5 ) )
857     OBJET_DE_TEST.store( (1., 1.) )
858     print "Les pas de stockage :", OBJET_DE_TEST.stepserie()
859     print "Les valeurs         :", OBJET_DE_TEST.valueserie()
860     print "La 2ème valeur      :", OBJET_DE_TEST.valueserie(1)
861     print "La dernière valeur  :", OBJET_DE_TEST.valueserie(-1)
862     print "Valeurs par pas :"
863     print "  La moyenne        :", OBJET_DE_TEST.mean()
864     print "  L'écart-type      :", OBJET_DE_TEST.std()
865     print "  La somme          :", OBJET_DE_TEST.sum()
866     print "  Le minimum        :", OBJET_DE_TEST.min()
867     print "  Le maximum        :", OBJET_DE_TEST.max()
868     print "Valeurs globales :"
869     print "  La moyenne        :", OBJET_DE_TEST.stepmean()
870     print "  L'écart-type      :", OBJET_DE_TEST.stepstd()
871     print "  La somme          :", OBJET_DE_TEST.stepsum()
872     print "  Le minimum        :", OBJET_DE_TEST.stepmin()
873     print "  Le maximum        :", OBJET_DE_TEST.stepmax()
874     print "  La somme cumulée  :", OBJET_DE_TEST.cumsum()
875     print "Taille \"shape\"      :", OBJET_DE_TEST.shape()
876     print "Taille \"len\"        :", len(OBJET_DE_TEST)
877     del OBJET_DE_TEST
878     print
879
880     print "======> Une liste hétérogène"
881     OBJET_DE_TEST = OneList("My list", unit="bool/cm")
882     OBJET_DE_TEST.store( (True , -5) )
883     OBJET_DE_TEST.store( (False,  5 ) )
884     OBJET_DE_TEST.store( (True ,  1.) )
885     print "Les pas de stockage :", OBJET_DE_TEST.stepserie()
886     print "Les valeurs         :", OBJET_DE_TEST.valueserie()
887     print "La 2ème valeur      :", OBJET_DE_TEST.valueserie(1)
888     print "La dernière valeur  :", OBJET_DE_TEST.valueserie(-1)
889     print "Valeurs par pas : attention, on peut les calculer car True=1, False=0, mais cela n'a pas de sens"
890     print "  La moyenne        :", OBJET_DE_TEST.mean()
891     print "  L'écart-type      :", OBJET_DE_TEST.std()
892     print "  La somme          :", OBJET_DE_TEST.sum()
893     print "  Le minimum        :", OBJET_DE_TEST.min()
894     print "  Le maximum        :", OBJET_DE_TEST.max()
895     print "Valeurs globales : attention, on peut les calculer car True=1, False=0, mais cela n'a pas de sens"
896     print "  La moyenne        :", OBJET_DE_TEST.stepmean()
897     print "  L'écart-type      :", OBJET_DE_TEST.stepstd()
898     print "  La somme          :", OBJET_DE_TEST.stepsum()
899     print "  Le minimum        :", OBJET_DE_TEST.stepmin()
900     print "  Le maximum        :", OBJET_DE_TEST.stepmax()
901     print "  La somme cumulée  :", OBJET_DE_TEST.cumsum()
902     print "Taille \"shape\"      :", OBJET_DE_TEST.shape()
903     print "Taille \"len\"        :", len(OBJET_DE_TEST)
904     del OBJET_DE_TEST
905     print
906
907     print "======> Utilisation directe de la classe Persistence"
908     OBJET_DE_TEST = Persistence("My object", unit="", basetype=int )
909     OBJET_DE_TEST.store( 1  )
910     OBJET_DE_TEST.store( 3 )
911     OBJET_DE_TEST.store( 7  )
912     print "Les pas de stockage :", OBJET_DE_TEST.stepserie()
913     print "Les valeurs         :", OBJET_DE_TEST.valueserie()
914     print "La 2ème valeur      :", OBJET_DE_TEST.valueserie(1)
915     print "La dernière valeur  :", OBJET_DE_TEST.valueserie(-1)
916     print "Taille \"shape\"      :", OBJET_DE_TEST.shape()
917     print "Taille \"len\"        :", len(OBJET_DE_TEST)
918     del OBJET_DE_TEST
919     print
920
921     print "======> Utilisation des méthodes d'accès de type dictionnaire"
922     OBJET_DE_TEST = OneScalar("My int", unit="cm", basetype=int)
923     for i in range(5):
924         OBJET_DE_TEST.store( 7+i )
925     print "Taille \"len\"        :", len(OBJET_DE_TEST)
926     print "Les pas de stockage :", OBJET_DE_TEST.keys()
927     print "Les valeurs         :", OBJET_DE_TEST.values()
928     print "Les paires          :", OBJET_DE_TEST.items()
929     del OBJET_DE_TEST
930     print
931
932     print "======> Persistence composite"
933     OBJET_DE_TEST = CompositePersistence("My CompositePersistence")
934     print "Objets stockables :", OBJET_DE_TEST.get_stored_objects()
935     print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
936     print "--> Stockage d'une valeur de Background"
937     OBJET_DE_TEST.store("Background",numpy.zeros(5))
938     print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
939     print "--> Ajout d'un objet nouveau par defaut, de type vecteur numpy par pas"
940     OBJET_DE_TEST.add_object("ValeursVectorielles")
941     OBJET_DE_TEST.store("ValeursVectorielles",numpy.zeros(5))
942     print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
943     print "--> Ajout d'un objet nouveau de type liste par pas"
944     OBJET_DE_TEST.add_object("ValeursList", persistenceType=OneList )
945     OBJET_DE_TEST.store("ValeursList",range(5))
946     print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
947     print "--> Ajout d'un objet nouveau, de type vecteur string par pas"
948     OBJET_DE_TEST.add_object("ValeursStr", persistenceType=Persistence, basetype=str )
949     OBJET_DE_TEST.store("ValeursStr","c020")
950     OBJET_DE_TEST.store("ValeursStr","c021")
951     print "Les valeurs       :", OBJET_DE_TEST.get_object("ValeursStr").valueserie()
952     print "Acces comme dict  :", OBJET_DE_TEST["ValeursStr"].stepserie()
953     print "Acces comme dict  :", OBJET_DE_TEST["ValeursStr"].valueserie()
954     print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
955     print "--> Suppression d'un objet"
956     OBJET_DE_TEST.del_object("ValeursVectorielles")
957     print "Objets actifs     :", OBJET_DE_TEST.get_stored_objects( hideVoidObjects = True )
958     print "--> Enregistrement de l'objet complet de Persistence composite"
959     OBJET_DE_TEST.save_composite("composite.pkl")
960     print
961
962     print "======> Affichage graphique d'objets stockés"
963     OBJET_DE_TEST = Persistence("My object", unit="", basetype=numpy.array)
964     D = OBJET_DE_TEST
965     vect1 = [1, 2, 1, 2, 1]
966     vect2 = [-3, -3, 0, -3, -3]
967     vect3 = [-1, 1, -5, 1, -1]
968     vect4 = 100*[0.29, 0.97, 0.73, 0.01, 0.20]
969     print "Stockage de 3 vecteurs de longueur identique"
970     D.store(vect1)
971     D.store(vect2)
972     D.store(vect3)
973     print "Affichage graphique de l'ensemble du stockage sur une même image"
974     D.stepplot(
975         title = "Tous les vecteurs",
976         filename="vecteurs.ps",
977         xlabel = "Axe X",
978         ylabel = "Axe Y",
979         pause = False )
980     print "Stockage d'un quatrième vecteur de longueur différente"
981     D.store(vect4)
982     print "Affichage graphique séparé du dernier stockage"
983     D.plot(
984         item  = 3,
985         title = "Vecteurs",
986         filename = "vecteur",
987         xlabel = "Axe X",
988         ylabel = "Axe Y",
989         pause = False )
990     print "Les images ont été stockées en fichiers Postscript"
991     print "Taille \"shape\" du dernier objet stocké",OBJET_DE_TEST.shape()
992     print "Taille \"len\" du dernier objet stocké",len(OBJET_DE_TEST)
993     del OBJET_DE_TEST
994     print
995
996     print "======> Affichage graphique dynamique d'objets"
997     OBJET_DE_TEST = Persistence("My object", unit="", basetype=float)
998     D = OBJET_DE_TEST
999     D.plot(
1000         dynamic = True,
1001         title   = "Valeur suivie",
1002         xlabel  = "Pas",
1003         ylabel  = "Valeur",
1004         pause   = False,
1005         )
1006     for i in range(1,11):
1007         D.store( i*i )
1008     print "Taille \"shape\" du dernier objet stocké",OBJET_DE_TEST.shape()
1009     print "Taille \"len\" du dernier objet stocké",len(OBJET_DE_TEST)
1010     print "Nombre d'objets stockés",OBJET_DE_TEST.stepnumber()
1011     del OBJET_DE_TEST
1012     print
1013
1014     print "======> Affectation simple d'observateurs dynamiques"
1015     def obs(var=None,info=None):
1016         print "  ---> Mise en oeuvre de l'observer"
1017         print "       var  =",var.valueserie(-1)
1018         print "       info =",info
1019     OBJET_DE_TEST = Persistence("My object", unit="", basetype=list)
1020     D = OBJET_DE_TEST
1021     D.setDataObserver( HookFunction = obs )
1022     for i in range(5):
1023         # print
1024         print "Action de 1 observer sur la variable observée, étape :",i
1025         D.store( [i, i, i] )
1026     del OBJET_DE_TEST
1027     print
1028
1029     print "======> Affectation multiple d'observateurs dynamiques"
1030     def obs(var=None,info=None):
1031         print "  ---> Mise en oeuvre de l'observer"
1032         print "       var  =",var.valueserie(-1)
1033         print "       info =",info
1034     def obs_bis(var=None,info=None):
1035         print "  ---> Mise en oeuvre de l'observer"
1036         print "       var  =",var.valueserie(-1)
1037         print "       info =",info
1038     OBJET_DE_TEST = Persistence("My object", unit="", basetype=list)
1039     D = OBJET_DE_TEST
1040     D.setDataObserver(
1041         HookFunction   = obs,
1042         Scheduler      = [2, 4],
1043         HookParameters = "Premier observer",
1044         )
1045     D.setDataObserver(
1046         HookFunction   = obs,
1047         Scheduler      = xrange(1,3),
1048         HookParameters = "Second observer",
1049         )
1050     D.setDataObserver(
1051         HookFunction   = obs_bis,
1052         Scheduler      = range(1,3)+range(7,9),
1053         HookParameters = "Troisième observer",
1054         )
1055     for i in range(5):
1056         print "Action de 3 observers sur la variable observée, étape :",i
1057         D.store( [i, i, i] )
1058     D.removeDataObserver(
1059         HookFunction   = obs,
1060         )
1061     for i in range(5,10):
1062         print "Action d'un seul observer sur la variable observée, étape :",i
1063         D.store( [i, i, i] )
1064     del OBJET_DE_TEST
1065     print