Salome HOME
Minor documentation and source improvements
[modules/adao.git] / src / daComposant / daCore / Persistence.py
1 #-*-coding:iso-8859-1-*-
2 #
3 #  Copyright (C) 2008-2015 EDF R&D
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 #  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21 #  Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
22
23 """
24     Définit des outils de persistence et d'enregistrement de séries de valeurs
25     pour analyse ultérieure ou utilisation de calcul.
26 """
27 __author__ = "Jean-Philippe ARGAUD"
28 __all__ = []
29
30 import numpy, copy
31
32 from PlatformInfo import PathManagement ; PathManagement()
33
34 # ==============================================================================
35 class Persistence:
36     """
37     Classe générale de persistence définissant les accesseurs nécessaires
38     (Template)
39     """
40     def __init__(self, name="", unit="", basetype=str):
41         """
42         name : nom courant
43         unit : unité
44         basetype : type de base de l'objet stocké à chaque pas
45         
46         La gestion interne des données est exclusivement basée sur les variables
47         initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
48         objets comme des attributs) :
49         __basetype : le type de base de chaque valeur, sous la forme d'un type
50                      permettant l'instanciation ou le casting Python 
51         __values : les valeurs de stockage. Par défaut, c'est None
52         """
53         self.__name = str(name)
54         self.__unit = str(unit)
55         #
56         self.__basetype = basetype
57         #
58         self.__values = []
59         self.__tags   = []
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, **kwargs):
75         """
76         Stocke une valeur avec ses informations de filtrage.
77         """
78         if value is None: raise ValueError("Value argument required")
79         #
80         self.__values.append(copy.copy(self.__basetype(value)))
81         self.__tags.append(kwargs)
82         #
83         if self.__dynamic: self.__replots()
84         __step = len(self.__values) - 1
85         for hook, parameters, scheduler in self.__dataobservers:
86             if __step in scheduler:
87                 hook( self, parameters )
88
89     def pop(self, item=None):
90         """
91         Retire une valeur enregistree par son index de stockage. Sans argument,
92         retire le dernier objet enregistre.
93         """
94         if item is not None:
95             __index = int(item)
96             self.__values.pop(__index)
97             self.__tags.pop(__index)
98         else:
99             self.__values.pop()
100             self.__tags.pop()
101     
102     def shape(self):
103         """
104         Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un
105         objet numpy, renvoie le shape. Si c'est un entier, un flottant, un
106         complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la
107         longueur. Par défaut, renvoie 1.
108         """
109         if len(self.__values) > 0:
110             if self.__basetype in [numpy.matrix, numpy.array, numpy.ravel]:
111                 return self.__values[-1].shape
112             elif self.__basetype in [int, float]:
113                 return (1,)
114             elif self.__basetype in [list, dict]:
115                 return (len(self.__values[-1]),)
116             else:
117                 return (1,)
118         else:
119             raise ValueError("Object has no shape before its first storage")
120
121     # ---------------------------------------------------------
122     def __str__(self):
123         msg  = "   Index        Value   Tags\n"
124         for i,v in enumerate(self.__values):
125             msg += "  i=%05i  %10s   %s\n"%(i,v,self.__tags[i])
126         return msg
127     
128     def __len__(self):
129         return len(self.__values)
130     
131     def __getitem__(self, index=None ):
132         return copy.copy(self.__values[index])
133     
134     def count(self, value):
135         return self.__values.count(value)
136     
137     def index(self, value, start=0, stop=None):
138         if stop is None : stop = len(self.__values)
139         return self.__values.index(value, start, stop)
140
141     # ---------------------------------------------------------
142     def __filteredIndexes(self, **kwargs):
143         __indexOfFilteredItems = range(len(self.__tags))
144         __filteringKwTags = kwargs.keys()
145         if len(__filteringKwTags) > 0:
146             for tagKey in __filteringKwTags:
147                 __tmp = []
148                 for i in __indexOfFilteredItems:
149                     if self.__tags[i].has_key(tagKey):
150                         if self.__tags[i][tagKey] == kwargs[tagKey]:
151                             __tmp.append( i )
152                         elif isinstance(kwargs[tagKey],(list,tuple)) and self.__tags[i][tagKey] in kwargs[tagKey]:
153                             __tmp.append( i )
154                 __indexOfFilteredItems = __tmp
155                 if len(__indexOfFilteredItems) == 0: break
156         return __indexOfFilteredItems
157
158     # ---------------------------------------------------------
159     def values(self, **kwargs):
160         __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
161         return [self.__values[i] for i in __indexOfFilteredItems]
162
163     def keys(self, keyword=None , **kwargs):
164         __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
165         __keys = []
166         for i in __indexOfFilteredItems:
167             if self.__tags[i].has_key( keyword ):
168                 __keys.append( self.__tags[i][keyword] )
169             else:
170                 __keys.append( None )
171         return __keys
172
173     def items(self, keyword=None , **kwargs):
174         __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
175         __pairs = []
176         for i in __indexOfFilteredItems:
177             if self.__tags[i].has_key( keyword ):
178                 __pairs.append( [self.__tags[i][keyword], self.__values[i]] )
179             else:
180                 __pairs.append( [None, self.__values[i]] )
181         return __pairs
182
183     def tagkeys(self):
184         __allKeys = []
185         for dicotags in self.__tags:
186             __allKeys.extend( dicotags.keys() )
187         __allKeys = list(set(__allKeys))
188         __allKeys.sort()
189         return __allKeys
190
191     # def valueserie(self, item=None, allSteps=True, **kwargs):
192     #     if item is not None:
193     #         return self.__values[item]
194     #     else:
195     #         __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
196     #         if not allSteps and len(__indexOfFilteredItems) > 0:
197     #             return self.__values[__indexOfFilteredItems[0]]
198     #         else:
199     #             return [self.__values[i] for i in __indexOfFilteredItems]
200
201     def tagserie(self, item=None, withValues=False, outputTag=None, **kwargs):
202         if item is None:
203             __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
204         else:
205             __indexOfFilteredItems = [item,]
206         #
207         # Dans le cas où la sortie donne les valeurs d'un "outputTag"
208         if outputTag is not None and type(outputTag) is str :
209             outputValues = []
210             for index in __indexOfFilteredItems:
211                 if outputTag in self.__tags[index].keys():
212                     outputValues.append( self.__tags[index][outputTag] )
213             outputValues = list(set(outputValues))
214             outputValues.sort()
215             return outputValues
216         #
217         # Dans le cas où la sortie donne les tags satisfaisants aux conditions
218         else:
219             if withValues:
220                 return [self.__tags[index] for index in __indexOfFilteredItems]
221             else:
222                 allTags = {}
223                 for index in __indexOfFilteredItems:
224                     allTags.update( self.__tags[index] )
225                 allKeys = allTags.keys()
226                 allKeys.sort()
227                 return allKeys
228
229     # ---------------------------------------------------------
230     # Pour compatibilite
231     def stepnumber(self):
232         return len(self.__values)
233
234     # Pour compatibilite
235     def stepserie(self, **kwargs):
236         __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
237         return __indexOfFilteredItems
238
239     # ---------------------------------------------------------
240     def means(self):
241         """
242         Renvoie la série, contenant à chaque pas, la valeur moyenne des données
243         au pas. Il faut que le type de base soit compatible avec les types
244         élémentaires numpy.
245         """
246         try:
247             return [numpy.matrix(item).mean() for item in self.__values]
248         except:
249             raise TypeError("Base type is incompatible with numpy")
250
251     def stds(self, ddof=0):
252         """
253         Renvoie la série, contenant à chaque pas, l'écart-type des données
254         au pas. Il faut que le type de base soit compatible avec les types
255         élémentaires numpy.
256         
257         ddof : c'est le nombre de degrés de liberté pour le calcul de
258                l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
259         """
260         try:
261             if numpy.version.version >= '1.1.0':
262                 return [numpy.matrix(item).std(ddof=ddof) for item in self.__values]
263             else:
264                 return [numpy.matrix(item).std() for item in self.__values]
265         except:
266             raise TypeError("Base type is incompatible with numpy")
267
268     def sums(self):
269         """
270         Renvoie la série, contenant à chaque pas, la somme des données au pas.
271         Il faut que le type de base soit compatible avec les types élémentaires
272         numpy.
273         """
274         try:
275             return [numpy.matrix(item).sum() for item in self.__values]
276         except:
277             raise TypeError("Base type is incompatible with numpy")
278
279     def mins(self):
280         """
281         Renvoie la série, contenant à chaque pas, le minimum des données au pas.
282         Il faut que le type de base soit compatible avec les types élémentaires
283         numpy.
284         """
285         try:
286             return [numpy.matrix(item).min() for item in self.__values]
287         except:
288             raise TypeError("Base type is incompatible with numpy")
289
290     def maxs(self):
291         """
292         Renvoie la série, contenant à chaque pas, la maximum des données au pas.
293         Il faut que le type de base soit compatible avec les types élémentaires
294         numpy.
295         """
296         try:
297             return [numpy.matrix(item).max() for item in self.__values]
298         except:
299             raise TypeError("Base type is incompatible with numpy")
300
301     def __preplots(self,
302             title    = "",
303             xlabel   = "",
304             ylabel   = "",
305             ltitle   = None,
306             geometry = "600x400",
307             persist  = False,
308             pause    = True,
309             ):
310         #
311         # Vérification de la disponibilité du module Gnuplot
312         try:
313             import Gnuplot
314             self.__gnuplot = Gnuplot
315         except:
316             raise ImportError("The Gnuplot module is required to plot the object.")
317         #
318         # Vérification et compléments sur les paramètres d'entrée
319         if persist:
320             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
321         else:
322             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
323         if ltitle is None:
324             ltitle = ""
325         self.__g = self.__gnuplot.Gnuplot() # persist=1
326         self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term)
327         self.__g('set style data lines')
328         self.__g('set grid')
329         self.__g('set autoscale')
330         self.__g('set xlabel "'+str(xlabel).encode('ascii','replace')+'"')
331         self.__g('set ylabel "'+str(ylabel).encode('ascii','replace')+'"')
332         self.__title  = title
333         self.__ltitle = ltitle
334         self.__pause  = pause
335
336     def plots(self, item=None, step=None,
337             steps    = None,
338             title    = "",
339             xlabel   = "",
340             ylabel   = "",
341             ltitle   = None,
342             geometry = "600x400",
343             filename = "",
344             dynamic  = False,
345             persist  = False,
346             pause    = True,
347             ):
348         """
349         Renvoie un affichage de la valeur à chaque pas, si elle est compatible
350         avec un affichage Gnuplot (donc essentiellement un vecteur). Si
351         l'argument "step" existe dans la liste des pas de stockage effectués,
352         renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
353         "item" est correct, renvoie l'affichage de la valeur stockée au numéro
354         "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
355         affichage successif de tous les pas.
356
357         Arguments :
358             - step     : valeur du pas à afficher
359             - item     : index de la valeur à afficher
360             - steps    : liste unique des pas de l'axe des X, ou None si c'est
361                          la numérotation par défaut
362             - title    : base du titre général, qui sera automatiquement
363                          complétée par la mention du pas
364             - xlabel   : label de l'axe des X
365             - ylabel   : label de l'axe des Y
366             - ltitle   : titre associé au vecteur tracé
367             - geometry : taille en pixels de la fenêtre et position du coin haut
368                          gauche, au format X11 : LxH+X+Y (défaut : 600x400)
369             - filename : base de nom de fichier Postscript pour une sauvegarde,
370                          qui est automatiquement complétée par le numéro du
371                          fichier calculé par incrément simple de compteur
372             - dynamic  : effectue un affichage des valeurs à chaque stockage
373                          (au-delà du second). La méthode "plots" permet de
374                          déclarer l'affichage dynamique, et c'est la méthode
375                          "__replots" qui est utilisée pour l'effectuer
376             - persist  : booléen indiquant que la fenêtre affichée sera
377                          conservée lors du passage au dessin suivant
378                          Par défaut, persist = False
379             - pause    : booléen indiquant une pause après chaque tracé, et
380                          attendant un Return
381                          Par défaut, pause = True
382         """
383         import os
384         if not self.__dynamic:
385             self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
386             if dynamic:
387                 self.__dynamic = True
388                 if len(self.__values) == 0: return 0
389         #
390         # Tracé du ou des vecteurs demandés
391         indexes = []
392         if step is not None and step < len(self.__values):
393             indexes.append(step)
394         elif item is not None and item < len(self.__values):
395             indexes.append(item)
396         else:
397             indexes = indexes + range(len(self.__values))
398         #
399         i = -1
400         for index in indexes:
401             self.__g('set title  "'+str(title).encode('ascii','replace')+' (pas '+str(index)+')"')
402             if ( type(steps) is list ) or ( type(steps) is type(numpy.array([])) ):
403                 Steps = list(steps)
404             else:
405                 Steps = range(len(self.__values[index]))
406             #
407             self.__g.plot( self.__gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
408             #
409             if filename != "":
410                 i += 1
411                 stepfilename = "%s_%03i.ps"%(filename,i)
412                 if os.path.isfile(stepfilename):
413                     raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
414                 self.__g.hardcopy(filename=stepfilename, color=1)
415             if self.__pause:
416                 raw_input('Please press return to continue...\n')
417
418     def __replots(self):
419         """
420         Affichage dans le cas du suivi dynamique de la variable
421         """
422         if self.__dynamic and len(self.__values) < 2: return 0
423         #
424         self.__g('set title  "'+str(self.__title).encode('ascii','replace'))
425         Steps = range(len(self.__values))
426         self.__g.plot( self.__gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
427         #
428         if self.__pause:
429             raw_input('Please press return to continue...\n')
430
431     # ---------------------------------------------------------
432     def mean(self):
433         """
434         Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
435         longueur des pas. Il faut que le type de base soit compatible avec
436         les types élémentaires numpy.
437         """
438         try:
439             if self.__basetype in [int, float]:
440                 return float( numpy.array(self.__values).mean() )
441             else:
442                 return numpy.array(self.__values).mean(axis=0)
443         except:
444             raise TypeError("Base type is incompatible with numpy")
445
446     def std(self, ddof=0):
447         """
448         Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
449         longueur des pas. Il faut que le type de base soit compatible avec
450         les types élémentaires numpy.
451         
452         ddof : c'est le nombre de degrés de liberté pour le calcul de
453                l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
454         """
455         try:
456             if numpy.version.version >= '1.1.0':
457                 return numpy.array(self.__values).std(ddof=ddof,axis=0)
458             else:
459                 return numpy.array(self.__values).std(axis=0)
460         except:
461             raise TypeError("Base type is incompatible with numpy")
462
463     def sum(self):
464         """
465         Renvoie la somme de toutes les valeurs sans tenir compte de la
466         longueur des pas. Il faut que le type de base soit compatible avec
467         les types élémentaires numpy.
468         """
469         try:
470             return numpy.array(self.__values).sum(axis=0)
471         except:
472             raise TypeError("Base type is incompatible with numpy")
473
474     def min(self):
475         """
476         Renvoie le minimum de toutes les valeurs sans tenir compte de la
477         longueur des pas. Il faut que le type de base soit compatible avec
478         les types élémentaires numpy.
479         """
480         try:
481             return numpy.array(self.__values).min(axis=0)
482         except:
483             raise TypeError("Base type is incompatible with numpy")
484
485     def max(self):
486         """
487         Renvoie le maximum de toutes les valeurs sans tenir compte de la
488         longueur des pas. Il faut que le type de base soit compatible avec
489         les types élémentaires numpy.
490         """
491         try:
492             return numpy.array(self.__values).max(axis=0)
493         except:
494             raise TypeError("Base type is incompatible with numpy")
495
496     def cumsum(self):
497         """
498         Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
499         longueur des pas. Il faut que le type de base soit compatible avec
500         les types élémentaires numpy.
501         """
502         try:
503             return numpy.array(self.__values).cumsum(axis=0)
504         except:
505             raise TypeError("Base type is incompatible with numpy")
506
507     # On pourrait aussi utiliser les autres attributs d'une "matrix", comme
508     # "tofile", "min"...
509
510     def plot(self,
511             steps    = None,
512             title    = "",
513             xlabel   = "",
514             ylabel   = "",
515             ltitle   = None,
516             geometry = "600x400",
517             filename = "",
518             persist  = False,
519             pause    = True,
520             ):
521         """
522         Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
523         elles sont compatibles avec un affichage Gnuplot (donc essentiellement
524         un vecteur). Si l'argument "step" existe dans la liste des pas de
525         stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
526         "step". Si l'argument "item" est correct, renvoie l'affichage de la
527         valeur stockée au numéro "item".
528
529         Arguments :
530             - steps    : liste unique des pas de l'axe des X, ou None si c'est
531                          la numérotation par défaut
532             - title    : base du titre général, qui sera automatiquement
533                          complétée par la mention du pas
534             - xlabel   : label de l'axe des X
535             - ylabel   : label de l'axe des Y
536             - ltitle   : titre associé au vecteur tracé
537             - geometry : taille en pixels de la fenêtre et position du coin haut
538                          gauche, au format X11 : LxH+X+Y (défaut : 600x400)
539             - filename : nom de fichier Postscript pour une sauvegarde
540             - persist  : booléen indiquant que la fenêtre affichée sera
541                          conservée lors du passage au dessin suivant
542                          Par défaut, persist = False
543             - pause    : booléen indiquant une pause après chaque tracé, et
544                          attendant un Return
545                          Par défaut, pause = True
546         """
547         #
548         # Vérification de la disponibilité du module Gnuplot
549         try:
550             import Gnuplot
551             self.__gnuplot = Gnuplot
552         except:
553             raise ImportError("The Gnuplot module is required to plot the object.")
554         #
555         # Vérification et compléments sur les paramètres d'entrée
556         if persist:
557             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
558         else:
559             self.__gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
560         if ltitle is None:
561             ltitle = ""
562         if ( type(steps) is list ) or ( type(steps) is type(numpy.array([])) ):
563             Steps = list(steps)
564         else:
565             Steps = range(len(self.__values[0]))
566         self.__g = self.__gnuplot.Gnuplot() # persist=1
567         self.__g('set terminal '+self.__gnuplot.GnuplotOpts.default_term)
568         self.__g('set style data lines')
569         self.__g('set grid')
570         self.__g('set autoscale')
571         self.__g('set title  "'+str(title).encode('ascii','replace') +'"')
572         self.__g('set xlabel "'+str(xlabel).encode('ascii','replace')+'"')
573         self.__g('set ylabel "'+str(ylabel).encode('ascii','replace')+'"')
574         #
575         # Tracé du ou des vecteurs demandés
576         indexes = range(len(self.__values))
577         self.__g.plot( self.__gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle+" (pas 0)" ) )
578         for index in indexes:
579             self.__g.replot( self.__gnuplot.Data( Steps, self.__values[index], title=ltitle+" (pas %i)"%index ) )
580         #
581         if filename != "":
582             self.__g.hardcopy(filename=filename, color=1)
583         if pause:
584             raw_input('Please press return to continue...\n')
585
586     # ---------------------------------------------------------
587     def setDataObserver(self,
588         HookFunction   = None,
589         HookParameters = None,
590         Scheduler      = None,
591         ):
592         """
593         Association à la variable d'un triplet définissant un observer
594         
595         Le Scheduler attendu est une fréquence, une simple liste d'index ou un
596         xrange des index.
597         """
598         #
599         # Vérification du Scheduler
600         # -------------------------
601         maxiter = int( 1e9 )
602         if type(Scheduler) is int:    # Considéré comme une fréquence à partir de 0
603             Schedulers = xrange( 0, maxiter, int(Scheduler) )
604         elif type(Scheduler) is xrange: # Considéré comme un itérateur
605             Schedulers = Scheduler
606         elif type(Scheduler) is list: # Considéré comme des index explicites
607             Schedulers = map( long, Scheduler )
608         else:                         # Dans tous les autres cas, activé par défaut
609             Schedulers = xrange( 0, maxiter )
610         #
611         # Stockage interne de l'observer dans la variable
612         # -----------------------------------------------
613         self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
614
615     def removeDataObserver(self,
616         HookFunction   = None,
617         ):
618         """
619         Suppression d'un observer nommé sur la variable.
620         
621         On peut donner dans HookFunction la meme fonction que lors de la
622         définition, ou un simple string qui est le nom de la fonction.
623         
624         """
625         if hasattr(HookFunction,"func_name"):
626             name = str( HookFunction.func_name )
627         elif type(HookFunction) is str:
628             name = str( HookFunction )
629         else:
630             name = None
631         #
632         i = -1
633         index_to_remove = []
634         for [hf, hp, hs] in self.__dataobservers:
635             i = i + 1
636             if name is hf.func_name: index_to_remove.append( i )
637         index_to_remove.reverse()
638         for i in index_to_remove:
639                 self.__dataobservers.pop( i )
640
641 # ==============================================================================
642 class OneScalar(Persistence):
643     """
644     Classe définissant le stockage d'une valeur unique réelle (float) par pas.
645
646     Le type de base peut être changé par la méthode "basetype", mais il faut que
647     le nouveau type de base soit compatible avec les types par éléments de
648     numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
649     ou des matrices comme dans les classes suivantes, mais c'est déconseillé
650     pour conserver une signification claire des noms.
651     """
652     def __init__(self, name="", unit="", basetype = float):
653         Persistence.__init__(self, name, unit, basetype)
654
655 class OneVector(Persistence):
656     """
657     Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
658     pas utiliser cette classe pour des données hétérogènes, mais "OneList".
659     """
660     def __init__(self, name="", unit="", basetype = numpy.ravel):
661         Persistence.__init__(self, name, unit, basetype)
662
663 class OneMatrix(Persistence):
664     """
665     Classe de stockage d'une matrice de valeurs (numpy.matrix) par pas.
666     """
667     def __init__(self, name="", unit="", basetype = numpy.matrix):
668         Persistence.__init__(self, name, unit, basetype)
669
670 class OneList(Persistence):
671     """
672     Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne pas
673     utiliser cette classe pour des données numériques homogènes, mais
674     "OneVector".
675     """
676     def __init__(self, name="", unit="", basetype = list):
677         Persistence.__init__(self, name, unit, basetype)
678
679 def NoType( value ): return value
680
681 class OneNoType(Persistence):
682     """
683     Classe de stockage d'un objet sans modification (cast) de type. Attention,
684     selon le véritable type de l'objet stocké à chaque pas, les opérations
685     arithmétiques à base de numpy peuvent être invalides ou donner des résultats
686     inattendus. Cette classe n'est donc à utiliser qu'à bon escient
687     volontairement, et pas du tout par défaut.
688     """
689     def __init__(self, name="", unit="", basetype = NoType):
690         Persistence.__init__(self, name, unit, basetype)
691
692 # ==============================================================================
693 class CompositePersistence:
694     """
695     Structure de stockage permettant de rassembler plusieurs objets de
696     persistence.
697     
698     Des objets par défaut sont prévus, et des objets supplémentaires peuvent
699     être ajoutés.
700     """
701     def __init__(self, name="", defaults=True):
702         """
703         name : nom courant
704         
705         La gestion interne des données est exclusivement basée sur les variables
706         initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
707         objets comme des attributs) :
708         __StoredObjects : objets de type persistence collectés dans cet objet
709         """
710         self.__name = str(name)
711         #
712         self.__StoredObjects = {}
713         #
714         # Definition des objets par defaut
715         # --------------------------------
716         if defaults:
717             self.__StoredObjects["Informations"]     = OneNoType("Informations")
718             self.__StoredObjects["Background"]       = OneVector("Background", basetype=numpy.array)
719             self.__StoredObjects["BackgroundError"]  = OneMatrix("BackgroundError")
720             self.__StoredObjects["Observation"]      = OneVector("Observation", basetype=numpy.array)
721             self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
722             self.__StoredObjects["Analysis"]         = OneVector("Analysis", basetype=numpy.array)
723             self.__StoredObjects["AnalysisError"]    = OneMatrix("AnalysisError")
724             self.__StoredObjects["Innovation"]       = OneVector("Innovation", basetype=numpy.array)
725             self.__StoredObjects["KalmanGainK"]      = OneMatrix("KalmanGainK")
726             self.__StoredObjects["OperatorH"]        = OneMatrix("OperatorH")
727             self.__StoredObjects["RmsOMA"]           = OneScalar("RmsOMA")
728             self.__StoredObjects["RmsOMB"]           = OneScalar("RmsOMB")
729             self.__StoredObjects["RmsBMA"]           = OneScalar("RmsBMA")
730         #
731
732     def store(self, name=None, value=None, **kwargs):
733         """
734         Stockage d'une valeur "value" pour le "step" dans la variable "name".
735         """
736         if name is None: raise ValueError("Storable object name is required for storage.")
737         if name not in self.__StoredObjects.keys():
738             raise ValueError("No such name '%s' exists in storable objects."%name)
739         self.__StoredObjects[name].store( value=value, **kwargs )
740
741     def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
742         """
743         Ajoute dans les objets stockables un nouvel objet défini par son nom, son
744         type de Persistence et son type de base à chaque pas.
745         """
746         if name is None: raise ValueError("Object name is required for adding an object.")
747         if name in self.__StoredObjects.keys():
748             raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
749         if basetype is None:
750             self.__StoredObjects[name] = persistenceType( name=str(name) )
751         else:
752             self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
753
754     def get_object(self, name=None ):
755         """
756         Renvoie l'objet de type Persistence qui porte le nom demandé.
757         """
758         if name is None: raise ValueError("Object name is required for retrieving an object.")
759         if name not in self.__StoredObjects.keys():
760             raise ValueError("No such name '%s' exists in stored objects."%name)
761         return self.__StoredObjects[name]
762
763     def set_object(self, name=None, objet=None ):
764         """
765         Affecte directement un 'objet' qui porte le nom 'name' demandé.
766         Attention, il n'est pas effectué de vérification sur le type, qui doit
767         comporter les méthodes habituelles de Persistence pour que cela
768         fonctionne.
769         """
770         if name is None: raise ValueError("Object name is required for setting an object.")
771         if name in self.__StoredObjects.keys():
772             raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
773         self.__StoredObjects[name] = objet
774
775     def del_object(self, name=None ):
776         """
777         Supprime un objet de la liste des objets stockables.
778         """
779         if name is None: raise ValueError("Object name is required for retrieving an object.")
780         if name not in self.__StoredObjects.keys():
781             raise ValueError("No such name '%s' exists in stored objects."%name)
782         del self.__StoredObjects[name]
783
784     # ---------------------------------------------------------
785     # Méthodes d'accès de type dictionnaire
786     def __getitem__(self, name=None ):
787         return self.get_object( name )
788
789     def __setitem__(self, name=None, objet=None ):
790         self.set_object( name, objet )
791
792     def keys(self):
793         return self.get_stored_objects(hideVoidObjects = False)
794
795     def values(self):
796         return self.__StoredObjects.values()
797
798     def items(self):
799         return self.__StoredObjects.items()
800
801     # ---------------------------------------------------------
802     def get_stored_objects(self, hideVoidObjects = False):
803         objs = self.__StoredObjects.keys()
804         if hideVoidObjects:
805             usedObjs = []
806             for k in objs:
807                 try:
808                     if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
809                 except:
810                     pass
811             objs = usedObjs
812         objs.sort()
813         return objs
814
815     # ---------------------------------------------------------
816     def save_composite(self, filename=None, mode="pickle", compress="gzip"):
817         """
818         Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
819         et renvoi le nom du fichier
820         """
821         import os
822         if filename is None:
823             if compress == "gzip":
824                 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
825             elif compress == "bzip2":
826                 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
827             else:
828                 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
829         else:
830             filename = os.path.abspath( filename )
831         #
832         import cPickle
833         if mode == "pickle":
834             if compress == "gzip":
835                 import gzip
836                 output = gzip.open( filename, 'wb')
837             elif compress == "bzip2":
838                 import bz2
839                 output = bz2.BZ2File( filename, 'wb')
840             else:
841                 output = open( filename, 'wb')
842             cPickle.dump(self, output)
843             output.close()
844         else:
845             raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
846         #
847         return filename
848
849     def load_composite(self, filename=None, mode="pickle", compress="gzip"):
850         """
851         Recharge un objet composite sauvé en fichier
852         """
853         import os
854         if filename is None:
855             raise ValueError("A file name if requested to load a composite.")
856         else:
857             filename = os.path.abspath( filename )
858         #
859         import cPickle
860         if mode == "pickle":
861             if compress == "gzip":
862                 import gzip
863                 pkl_file = gzip.open( filename, 'rb')
864             elif compress == "bzip2":
865                 import bz2
866                 pkl_file = bz2.BZ2File( filename, 'rb')
867             else:
868                 pkl_file = open(filename, 'rb')
869             output = cPickle.load(pkl_file)
870             for k in output.keys():
871                 self[k] = output[k]
872         else:
873             raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
874         #
875         return filename
876
877 # ==============================================================================
878 if __name__ == "__main__":
879     print '\n AUTODIAGNOSTIC \n'