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