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