Salome HOME
eecba7fb357ee295588f981c92a18206fa755a59
[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 OneIndex(Persistence):
656     """
657     Classe définissant le stockage d'une valeur unique entière (int) par pas.
658     """
659     def __init__(self, name="", unit="", basetype = int):
660         Persistence.__init__(self, name, unit, basetype)
661
662 class OneVector(Persistence):
663     """
664     Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
665     pas utiliser cette classe pour des données hétérogènes, mais "OneList".
666     """
667     def __init__(self, name="", unit="", basetype = numpy.ravel):
668         Persistence.__init__(self, name, unit, basetype)
669
670 class OneMatrix(Persistence):
671     """
672     Classe de stockage d'une matrice de valeurs (numpy.matrix) par pas.
673     """
674     def __init__(self, name="", unit="", basetype = numpy.matrix):
675         Persistence.__init__(self, name, unit, basetype)
676
677 class OneList(Persistence):
678     """
679     Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne pas
680     utiliser cette classe pour des données numériques homogènes, mais
681     "OneVector".
682     """
683     def __init__(self, name="", unit="", basetype = list):
684         Persistence.__init__(self, name, unit, basetype)
685
686 def NoType( value ): return value
687
688 class OneNoType(Persistence):
689     """
690     Classe de stockage d'un objet sans modification (cast) de type. Attention,
691     selon le véritable type de l'objet stocké à chaque pas, les opérations
692     arithmétiques à base de numpy peuvent être invalides ou donner des résultats
693     inattendus. Cette classe n'est donc à utiliser qu'à bon escient
694     volontairement, et pas du tout par défaut.
695     """
696     def __init__(self, name="", unit="", basetype = NoType):
697         Persistence.__init__(self, name, unit, basetype)
698
699 # ==============================================================================
700 class CompositePersistence:
701     """
702     Structure de stockage permettant de rassembler plusieurs objets de
703     persistence.
704     
705     Des objets par défaut sont prévus, et des objets supplémentaires peuvent
706     être ajoutés.
707     """
708     def __init__(self, name="", defaults=True):
709         """
710         name : nom courant
711         
712         La gestion interne des données est exclusivement basée sur les variables
713         initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
714         objets comme des attributs) :
715         __StoredObjects : objets de type persistence collectés dans cet objet
716         """
717         self.__name = str(name)
718         #
719         self.__StoredObjects = {}
720         #
721         # Definition des objets par defaut
722         # --------------------------------
723         if defaults:
724             self.__StoredObjects["Informations"]     = OneNoType("Informations")
725             self.__StoredObjects["Background"]       = OneVector("Background", basetype=numpy.array)
726             self.__StoredObjects["BackgroundError"]  = OneMatrix("BackgroundError")
727             self.__StoredObjects["Observation"]      = OneVector("Observation", basetype=numpy.array)
728             self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
729             self.__StoredObjects["Analysis"]         = OneVector("Analysis", basetype=numpy.array)
730             self.__StoredObjects["AnalysisError"]    = OneMatrix("AnalysisError")
731             self.__StoredObjects["Innovation"]       = OneVector("Innovation", basetype=numpy.array)
732             self.__StoredObjects["KalmanGainK"]      = OneMatrix("KalmanGainK")
733             self.__StoredObjects["OperatorH"]        = OneMatrix("OperatorH")
734             self.__StoredObjects["RmsOMA"]           = OneScalar("RmsOMA")
735             self.__StoredObjects["RmsOMB"]           = OneScalar("RmsOMB")
736             self.__StoredObjects["RmsBMA"]           = OneScalar("RmsBMA")
737         #
738
739     def store(self, name=None, value=None, **kwargs):
740         """
741         Stockage d'une valeur "value" pour le "step" dans la variable "name".
742         """
743         if name is None: raise ValueError("Storable object name is required for storage.")
744         if name not in self.__StoredObjects.keys():
745             raise ValueError("No such name '%s' exists in storable objects."%name)
746         self.__StoredObjects[name].store( value=value, **kwargs )
747
748     def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
749         """
750         Ajoute dans les objets stockables un nouvel objet défini par son nom, son
751         type de Persistence et son type de base à chaque pas.
752         """
753         if name is None: raise ValueError("Object name is required for adding an object.")
754         if name in self.__StoredObjects.keys():
755             raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
756         if basetype is None:
757             self.__StoredObjects[name] = persistenceType( name=str(name) )
758         else:
759             self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
760
761     def get_object(self, name=None ):
762         """
763         Renvoie l'objet de type Persistence qui porte le nom demandé.
764         """
765         if name is None: raise ValueError("Object name is required for retrieving an object.")
766         if name not in self.__StoredObjects.keys():
767             raise ValueError("No such name '%s' exists in stored objects."%name)
768         return self.__StoredObjects[name]
769
770     def set_object(self, name=None, objet=None ):
771         """
772         Affecte directement un 'objet' qui porte le nom 'name' demandé.
773         Attention, il n'est pas effectué de vérification sur le type, qui doit
774         comporter les méthodes habituelles de Persistence pour que cela
775         fonctionne.
776         """
777         if name is None: raise ValueError("Object name is required for setting an object.")
778         if name in self.__StoredObjects.keys():
779             raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
780         self.__StoredObjects[name] = objet
781
782     def del_object(self, name=None ):
783         """
784         Supprime un objet de la liste des objets stockables.
785         """
786         if name is None: raise ValueError("Object name is required for retrieving an object.")
787         if name not in self.__StoredObjects.keys():
788             raise ValueError("No such name '%s' exists in stored objects."%name)
789         del self.__StoredObjects[name]
790
791     # ---------------------------------------------------------
792     # Méthodes d'accès de type dictionnaire
793     def __getitem__(self, name=None ):
794         return self.get_object( name )
795
796     def __setitem__(self, name=None, objet=None ):
797         self.set_object( name, objet )
798
799     def keys(self):
800         return self.get_stored_objects(hideVoidObjects = False)
801
802     def values(self):
803         return self.__StoredObjects.values()
804
805     def items(self):
806         return self.__StoredObjects.items()
807
808     # ---------------------------------------------------------
809     def get_stored_objects(self, hideVoidObjects = False):
810         objs = self.__StoredObjects.keys()
811         if hideVoidObjects:
812             usedObjs = []
813             for k in objs:
814                 try:
815                     if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
816                 except:
817                     pass
818             objs = usedObjs
819         objs.sort()
820         return objs
821
822     # ---------------------------------------------------------
823     def save_composite(self, filename=None, mode="pickle", compress="gzip"):
824         """
825         Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
826         et renvoi le nom du fichier
827         """
828         import os
829         if filename is None:
830             if compress == "gzip":
831                 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
832             elif compress == "bzip2":
833                 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
834             else:
835                 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
836         else:
837             filename = os.path.abspath( filename )
838         #
839         import cPickle
840         if mode == "pickle":
841             if compress == "gzip":
842                 import gzip
843                 output = gzip.open( filename, 'wb')
844             elif compress == "bzip2":
845                 import bz2
846                 output = bz2.BZ2File( filename, 'wb')
847             else:
848                 output = open( filename, 'wb')
849             cPickle.dump(self, output)
850             output.close()
851         else:
852             raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
853         #
854         return filename
855
856     def load_composite(self, filename=None, mode="pickle", compress="gzip"):
857         """
858         Recharge un objet composite sauvé en fichier
859         """
860         import os
861         if filename is None:
862             raise ValueError("A file name if requested to load a composite.")
863         else:
864             filename = os.path.abspath( filename )
865         #
866         import cPickle
867         if mode == "pickle":
868             if compress == "gzip":
869                 import gzip
870                 pkl_file = gzip.open( filename, 'rb')
871             elif compress == "bzip2":
872                 import bz2
873                 pkl_file = bz2.BZ2File( filename, 'rb')
874             else:
875                 pkl_file = open(filename, 'rb')
876             output = cPickle.load(pkl_file)
877             for k in output.keys():
878                 self[k] = output[k]
879         else:
880             raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
881         #
882         return filename
883
884 # ==============================================================================
885 if __name__ == "__main__":
886     print '\n AUTODIAGNOSTIC \n'