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