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