1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008-2022 EDF R&D
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.
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.
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
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 # Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
24 Définit des outils de persistance et d'enregistrement de séries de valeurs
25 pour analyse ultérieure ou utilisation de calcul.
27 __author__ = "Jean-Philippe ARGAUD"
30 import os, numpy, copy, math
31 import gzip, bz2, pickle
33 from daCore.PlatformInfo import PathManagement ; PathManagement()
34 from daCore.PlatformInfo import has_gnuplot, PlatformInfo
35 mfp = PlatformInfo().MaximumPrecision()
39 # ==============================================================================
40 class Persistence(object):
42 Classe générale de persistance définissant les accesseurs nécessaires
45 def __init__(self, name="", unit="", basetype=str):
49 basetype : type de base de l'objet stocké à chaque pas
51 La gestion interne des données est exclusivement basée sur les variables
52 initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
53 objets comme des attributs) :
54 __basetype : le type de base de chaque valeur, sous la forme d'un type
55 permettant l'instanciation ou le casting Python
56 __values : les valeurs de stockage. Par défaut, c'est None
58 self.__name = str(name)
59 self.__unit = str(unit)
61 self.__basetype = basetype
66 self.__dynamic = False
72 self.__dataobservers = []
74 def basetype(self, basetype=None):
76 Renvoie ou met en place le type de base des objets stockés
79 return self.__basetype
81 self.__basetype = basetype
83 def store(self, value=None, **kwargs):
85 Stocke une valeur avec ses informations de filtrage.
87 if value is None: raise ValueError("Value argument required")
89 self.__values.append(copy.copy(self.__basetype(value)))
90 self.__tags.append(kwargs)
92 if self.__dynamic: self.__replots()
93 __step = len(self.__values) - 1
94 for hook, parameters, scheduler in self.__dataobservers:
95 if __step in scheduler:
96 hook( self, parameters )
98 def pop(self, item=None):
100 Retire une valeur enregistrée par son index de stockage. Sans argument,
101 retire le dernier objet enregistre.
105 self.__values.pop(__index)
106 self.__tags.pop(__index)
113 Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un
114 objet numpy, renvoie le shape. Si c'est un entier, un flottant, un
115 complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la
116 longueur. Par défaut, renvoie 1.
118 if len(self.__values) > 0:
119 if self.__basetype in [numpy.matrix, numpy.ndarray, numpy.array, numpy.ravel]:
120 return self.__values[-1].shape
121 elif self.__basetype in [int, float]:
123 elif self.__basetype in [list, dict]:
124 return (len(self.__values[-1]),)
128 raise ValueError("Object has no shape before its first storage")
130 # ---------------------------------------------------------
132 "x.__str__() <==> str(x)"
133 msg = " Index Value Tags\n"
134 for i,v in enumerate(self.__values):
135 msg += " i=%05i %10s %s\n"%(i,v,self.__tags[i])
139 "x.__len__() <==> len(x)"
140 return len(self.__values)
145 def __getitem__(self, index=None ):
146 "x.__getitem__(y) <==> x[y]"
147 return copy.copy(self.__values[index])
149 def count(self, value):
150 "L.count(value) -> integer -- return number of occurrences of value"
151 return self.__values.count(value)
153 def index(self, value, start=0, stop=None):
154 "L.index(value, [start, [stop]]) -> integer -- return first index of value."
155 if stop is None : stop = len(self.__values)
156 return self.__values.index(value, start, stop)
158 # ---------------------------------------------------------
159 def __filteredIndexes(self, **kwargs):
160 "Function interne filtrant les index"
161 __indexOfFilteredItems = range(len(self.__tags))
162 __filteringKwTags = kwargs.keys()
163 if len(__filteringKwTags) > 0:
164 for tagKey in __filteringKwTags:
166 for i in __indexOfFilteredItems:
167 if tagKey in self.__tags[i]:
168 if self.__tags[i][tagKey] == kwargs[tagKey]:
170 elif isinstance(kwargs[tagKey],(list,tuple)) and self.__tags[i][tagKey] in kwargs[tagKey]:
172 __indexOfFilteredItems = __tmp
173 if len(__indexOfFilteredItems) == 0: break
174 return __indexOfFilteredItems
176 # ---------------------------------------------------------
177 def values(self, **kwargs):
178 "D.values() -> list of D's values"
179 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
180 return [self.__values[i] for i in __indexOfFilteredItems]
182 def keys(self, keyword=None , **kwargs):
183 "D.keys() -> list of D's keys"
184 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
186 for i in __indexOfFilteredItems:
187 if keyword in self.__tags[i]:
188 __keys.append( self.__tags[i][keyword] )
190 __keys.append( None )
193 def items(self, keyword=None , **kwargs):
194 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
195 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
197 for i in __indexOfFilteredItems:
198 if keyword in self.__tags[i]:
199 __pairs.append( (self.__tags[i][keyword], self.__values[i]) )
201 __pairs.append( (None, self.__values[i]) )
205 "D.tagkeys() -> list of D's tag keys"
207 for dicotags in self.__tags:
208 __allKeys.extend( list(dicotags.keys()) )
209 __allKeys = sorted(set(__allKeys))
212 # def valueserie(self, item=None, allSteps=True, **kwargs):
213 # if item is not None:
214 # return self.__values[item]
216 # __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
217 # if not allSteps and len(__indexOfFilteredItems) > 0:
218 # return self.__values[__indexOfFilteredItems[0]]
220 # return [self.__values[i] for i in __indexOfFilteredItems]
222 def tagserie(self, item=None, withValues=False, outputTag=None, **kwargs):
223 "D.tagserie() -> list of D's tag serie"
225 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
227 __indexOfFilteredItems = [item,]
229 # Dans le cas où la sortie donne les valeurs d'un "outputTag"
230 if outputTag is not None and isinstance(outputTag,str) :
232 for index in __indexOfFilteredItems:
233 if outputTag in self.__tags[index].keys():
234 outputValues.append( self.__tags[index][outputTag] )
235 outputValues = sorted(set(outputValues))
238 # Dans le cas où la sortie donne les tags satisfaisants aux conditions
241 return [self.__tags[index] for index in __indexOfFilteredItems]
244 for index in __indexOfFilteredItems:
245 allTags.update( self.__tags[index] )
246 allKeys = sorted(allTags.keys())
249 # ---------------------------------------------------------
251 def stepnumber(self):
253 return len(self.__values)
256 def stepserie(self, **kwargs):
257 "Nombre de pas filtrés"
258 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
259 return __indexOfFilteredItems
262 def steplist(self, **kwargs):
263 "Nombre de pas filtrés"
264 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
265 return list(__indexOfFilteredItems)
267 # ---------------------------------------------------------
270 Renvoie la série, contenant à chaque pas, la valeur moyenne des données
271 au pas. Il faut que le type de base soit compatible avec les types
275 return [numpy.mean(item, dtype=mfp).astype('float') for item in self.__values]
277 raise TypeError("Base type is incompatible with numpy")
279 def stds(self, ddof=0):
281 Renvoie la série, contenant à chaque pas, l'écart-type des données
282 au pas. Il faut que le type de base soit compatible avec les types
285 ddof : c'est le nombre de degrés de liberté pour le calcul de
286 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
289 if numpy.version.version >= '1.1.0':
290 return [numpy.array(item).std(ddof=ddof, dtype=mfp).astype('float') for item in self.__values]
292 return [numpy.array(item).std(dtype=mfp).astype('float') for item in self.__values]
294 raise TypeError("Base type is incompatible with numpy")
298 Renvoie la série, contenant à chaque pas, la somme des données au pas.
299 Il faut que le type de base soit compatible avec les types élémentaires
303 return [numpy.array(item).sum() for item in self.__values]
305 raise TypeError("Base type is incompatible with numpy")
309 Renvoie la série, contenant à chaque pas, le minimum des données au pas.
310 Il faut que le type de base soit compatible avec les types élémentaires
314 return [numpy.array(item).min() for item in self.__values]
316 raise TypeError("Base type is incompatible with numpy")
320 Renvoie la série, contenant à chaque pas, la maximum des données au pas.
321 Il faut que le type de base soit compatible avec les types élémentaires
325 return [numpy.array(item).max() for item in self.__values]
327 raise TypeError("Base type is incompatible with numpy")
329 def norms(self, _ord=None):
331 Norm (_ord : voir numpy.linalg.norm)
333 Renvoie la série, contenant à chaque pas, la norme des données au pas.
334 Il faut que le type de base soit compatible avec les types élémentaires
338 return [numpy.linalg.norm(item, _ord) for item in self.__values]
340 raise TypeError("Base type is incompatible with numpy")
342 def maes(self, _predictor=None):
344 Mean Absolute Error (MAE)
345 mae(dX) = 1/n sum(dX_i)
347 Renvoie la série, contenant à chaque pas, la MAE des données au pas.
348 Il faut que le type de base soit compatible avec les types élémentaires
349 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
350 prédicteur est None, sinon c'est appliqué à l'écart entre les données
351 au pas et le prédicteur au même pas.
353 if _predictor is None:
355 return [numpy.mean(numpy.abs(item)) for item in self.__values]
357 raise TypeError("Base type is incompatible with numpy")
359 if len(_predictor) != len(self.__values):
360 raise ValueError("Predictor number of steps is incompatible with the values")
361 for i, item in enumerate(self.__values):
362 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
363 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
365 return [numpy.mean(numpy.abs(numpy.ravel(item) - numpy.ravel(_predictor[i]))) for i, item in enumerate(self.__values)]
367 raise TypeError("Base type is incompatible with numpy")
369 def mses(self, _predictor=None):
371 Mean-Square Error (MSE) ou Mean-Square Deviation (MSD)
372 mse(dX) = 1/n sum(dX_i**2)
374 Renvoie la série, contenant à chaque pas, la MSE des données au pas. Il
375 faut que le type de base soit compatible avec les types élémentaires
376 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
377 prédicteur est None, sinon c'est appliqué à l'écart entre les données
378 au pas et le prédicteur au même pas.
380 if _predictor is None:
382 __n = self.shape()[0]
383 return [(numpy.linalg.norm(item)**2 / __n) for item in self.__values]
385 raise TypeError("Base type is incompatible with numpy")
387 if len(_predictor) != len(self.__values):
388 raise ValueError("Predictor number of steps is incompatible with the values")
389 for i, item in enumerate(self.__values):
390 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
391 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
393 __n = self.shape()[0]
394 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i]))**2 / __n) for i, item in enumerate(self.__values)]
396 raise TypeError("Base type is incompatible with numpy")
398 msds=mses # Mean-Square Deviation (MSD=MSE)
400 def rmses(self, _predictor=None):
402 Root-Mean-Square Error (RMSE) ou Root-Mean-Square Deviation (RMSD)
403 rmse(dX) = sqrt( 1/n sum(dX_i**2) ) = sqrt( mse(dX) )
405 Renvoie la série, contenant à chaque pas, la RMSE des données au pas.
406 Il faut que le type de base soit compatible avec les types élémentaires
407 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
408 prédicteur est None, sinon c'est appliqué à l'écart entre les données
409 au pas et le prédicteur au même pas.
411 if _predictor is None:
413 __n = self.shape()[0]
414 return [(numpy.linalg.norm(item) / math.sqrt(__n)) for item in self.__values]
416 raise TypeError("Base type is incompatible with numpy")
418 if len(_predictor) != len(self.__values):
419 raise ValueError("Predictor number of steps is incompatible with the values")
420 for i, item in enumerate(self.__values):
421 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
422 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
424 __n = self.shape()[0]
425 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i])) / math.sqrt(__n)) for i, item in enumerate(self.__values)]
427 raise TypeError("Base type is incompatible with numpy")
429 rmsds = rmses # Root-Mean-Square Deviation (RMSD=RMSE)
436 geometry = "600x400",
440 "Préparation des plots"
442 # Vérification de la disponibilité du module Gnuplot
444 raise ImportError("The Gnuplot module is required to plot the object.")
446 # Vérification et compléments sur les paramètres d'entrée
448 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
450 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
453 self.__g = Gnuplot.Gnuplot() # persist=1
454 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term)
455 self.__g('set style data lines')
457 self.__g('set autoscale')
458 self.__g('set xlabel "'+str(xlabel)+'"')
459 self.__g('set ylabel "'+str(ylabel)+'"')
461 self.__ltitle = ltitle
472 geometry = "600x400",
479 Renvoie un affichage de la valeur à chaque pas, si elle est compatible
480 avec un affichage Gnuplot (donc essentiellement un vecteur). Si
481 l'argument "step" existe dans la liste des pas de stockage effectués,
482 renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
483 "item" est correct, renvoie l'affichage de la valeur stockée au numéro
484 "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
485 affichage successif de tous les pas.
488 - step : valeur du pas à afficher
489 - item : index de la valeur à afficher
490 - steps : liste unique des pas de l'axe des X, ou None si c'est
491 la numérotation par défaut
492 - title : base du titre général, qui sera automatiquement
493 complétée par la mention du pas
494 - xlabel : label de l'axe des X
495 - ylabel : label de l'axe des Y
496 - ltitle : titre associé au vecteur tracé
497 - geometry : taille en pixels de la fenêtre et position du coin haut
498 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
499 - filename : base de nom de fichier Postscript pour une sauvegarde,
500 qui est automatiquement complétée par le numéro du
501 fichier calculé par incrément simple de compteur
502 - dynamic : effectue un affichage des valeurs à chaque stockage
503 (au-delà du second). La méthode "plots" permet de
504 déclarer l'affichage dynamique, et c'est la méthode
505 "__replots" qui est utilisée pour l'effectuer
506 - persist : booléen indiquant que la fenêtre affichée sera
507 conservée lors du passage au dessin suivant
508 Par défaut, persist = False
509 - pause : booléen indiquant une pause après chaque tracé, et
511 Par défaut, pause = True
513 if not self.__dynamic:
514 self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
516 self.__dynamic = True
517 if len(self.__values) == 0: return 0
519 # Tracé du ou des vecteurs demandés
521 if step is not None and step < len(self.__values):
523 elif item is not None and item < len(self.__values):
526 indexes = indexes + list(range(len(self.__values)))
529 for index in indexes:
530 self.__g('set title "'+str(title)+' (pas '+str(index)+')"')
531 if isinstance(steps,list) or isinstance(steps,numpy.ndarray):
534 Steps = list(range(len(self.__values[index])))
536 self.__g.plot( Gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
540 stepfilename = "%s_%03i.ps"%(filename,i)
541 if os.path.isfile(stepfilename):
542 raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
543 self.__g.hardcopy(filename=stepfilename, color=1)
545 eval(input('Please press return to continue...\n'))
549 Affichage dans le cas du suivi dynamique de la variable
551 if self.__dynamic and len(self.__values) < 2: return 0
553 self.__g('set title "'+str(self.__title))
554 Steps = list(range(len(self.__values)))
555 self.__g.plot( Gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
558 eval(input('Please press return to continue...\n'))
560 # ---------------------------------------------------------
561 # On pourrait aussi utiliser d'autres attributs d'un "array" comme "tofile"
564 Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
565 longueur des pas. Il faut que le type de base soit compatible avec
566 les types élémentaires numpy.
569 return numpy.mean(self.__values, axis=0, dtype=mfp).astype('float')
571 raise TypeError("Base type is incompatible with numpy")
573 def std(self, ddof=0):
575 Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
576 longueur des pas. Il faut que le type de base soit compatible avec
577 les types élémentaires numpy.
579 ddof : c'est le nombre de degrés de liberté pour le calcul de
580 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
583 if numpy.version.version >= '1.1.0':
584 return numpy.asarray(self.__values).std(ddof=ddof,axis=0).astype('float')
586 return numpy.asarray(self.__values).std(axis=0).astype('float')
588 raise TypeError("Base type is incompatible with numpy")
592 Renvoie la somme de toutes les valeurs sans tenir compte de la
593 longueur des pas. Il faut que le type de base soit compatible avec
594 les types élémentaires numpy.
597 return numpy.asarray(self.__values).sum(axis=0)
599 raise TypeError("Base type is incompatible with numpy")
603 Renvoie le minimum de toutes les valeurs sans tenir compte de la
604 longueur des pas. Il faut que le type de base soit compatible avec
605 les types élémentaires numpy.
608 return numpy.asarray(self.__values).min(axis=0)
610 raise TypeError("Base type is incompatible with numpy")
614 Renvoie le maximum de toutes les valeurs sans tenir compte de la
615 longueur des pas. Il faut que le type de base soit compatible avec
616 les types élémentaires numpy.
619 return numpy.asarray(self.__values).max(axis=0)
621 raise TypeError("Base type is incompatible with numpy")
625 Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
626 longueur des pas. Il faut que le type de base soit compatible avec
627 les types élémentaires numpy.
630 return numpy.asarray(self.__values).cumsum(axis=0)
632 raise TypeError("Base type is incompatible with numpy")
640 geometry = "600x400",
646 Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
647 elles sont compatibles avec un affichage Gnuplot (donc essentiellement
648 un vecteur). Si l'argument "step" existe dans la liste des pas de
649 stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
650 "step". Si l'argument "item" est correct, renvoie l'affichage de la
651 valeur stockée au numéro "item".
654 - steps : liste unique des pas de l'axe des X, ou None si c'est
655 la numérotation par défaut
656 - title : base du titre général, qui sera automatiquement
657 complétée par la mention du pas
658 - xlabel : label de l'axe des X
659 - ylabel : label de l'axe des Y
660 - ltitle : titre associé au vecteur tracé
661 - geometry : taille en pixels de la fenêtre et position du coin haut
662 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
663 - filename : nom de fichier Postscript pour une sauvegarde
664 - persist : booléen indiquant que la fenêtre affichée sera
665 conservée lors du passage au dessin suivant
666 Par défaut, persist = False
667 - pause : booléen indiquant une pause après chaque tracé, et
669 Par défaut, pause = True
672 # Vérification de la disponibilité du module Gnuplot
674 raise ImportError("The Gnuplot module is required to plot the object.")
676 # Vérification et compléments sur les paramètres d'entrée
678 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
680 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
683 if isinstance(steps,list) or isinstance(steps, numpy.ndarray):
686 Steps = list(range(len(self.__values[0])))
687 self.__g = Gnuplot.Gnuplot() # persist=1
688 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term)
689 self.__g('set style data lines')
691 self.__g('set autoscale')
692 self.__g('set title "'+str(title) +'"')
693 self.__g('set xlabel "'+str(xlabel)+'"')
694 self.__g('set ylabel "'+str(ylabel)+'"')
696 # Tracé du ou des vecteurs demandés
697 indexes = list(range(len(self.__values)))
698 self.__g.plot( Gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle+" (pas 0)" ) )
699 for index in indexes:
700 self.__g.replot( Gnuplot.Data( Steps, self.__values[index], title=ltitle+" (pas %i)"%index ) )
703 self.__g.hardcopy(filename=filename, color=1)
705 eval(input('Please press return to continue...\n'))
707 # ---------------------------------------------------------
708 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
710 Association à la variable d'un triplet définissant un observer
712 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
716 # Vérification du Scheduler
717 # -------------------------
719 if isinstance(Scheduler,int): # Considéré comme une fréquence à partir de 0
720 Schedulers = range( 0, maxiter, int(Scheduler) )
721 elif isinstance(Scheduler,range): # Considéré comme un itérateur
722 Schedulers = Scheduler
723 elif isinstance(Scheduler,(list,tuple)): # Considéré comme des index explicites
724 Schedulers = [int(i) for i in Scheduler] # map( long, Scheduler )
725 else: # Dans tous les autres cas, activé par défaut
726 Schedulers = range( 0, maxiter )
728 # Stockage interne de l'observer dans la variable
729 # -----------------------------------------------
730 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
732 def removeDataObserver(self, HookFunction = None, AllObservers = False):
734 Suppression d'un observer nommé sur la variable.
736 On peut donner dans HookFunction la meme fonction que lors de la
737 définition, ou un simple string qui est le nom de la fonction. Si
738 AllObservers est vrai, supprime tous les observers enregistrés.
740 if hasattr(HookFunction,"func_name"):
741 name = str( HookFunction.func_name )
742 elif hasattr(HookFunction,"__name__"):
743 name = str( HookFunction.__name__ )
744 elif isinstance(HookFunction,str):
745 name = str( HookFunction )
751 for [hf, hp, hs] in self.__dataobservers:
753 if name is hf.__name__ or AllObservers: index_to_remove.append( i )
754 index_to_remove.reverse()
755 for i in index_to_remove:
756 self.__dataobservers.pop( i )
757 return len(index_to_remove)
759 def hasDataObserver(self):
760 return bool(len(self.__dataobservers) > 0)
762 # ==============================================================================
763 class SchedulerTrigger(object):
765 Classe générale d'interface de type Scheduler/Trigger
768 simplifiedCombo = None,
770 endTime = int( 1e9 ),
777 # ==============================================================================
778 class OneScalar(Persistence):
780 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
782 Le type de base peut être changé par la méthode "basetype", mais il faut que
783 le nouveau type de base soit compatible avec les types par éléments de
784 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
785 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
786 pour conserver une signification claire des noms.
788 def __init__(self, name="", unit="", basetype = float):
789 Persistence.__init__(self, name, unit, basetype)
791 class OneIndex(Persistence):
793 Classe définissant le stockage d'une valeur unique entière (int) par pas.
795 def __init__(self, name="", unit="", basetype = int):
796 Persistence.__init__(self, name, unit, basetype)
798 class OneVector(Persistence):
800 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
801 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
803 def __init__(self, name="", unit="", basetype = numpy.ravel):
804 Persistence.__init__(self, name, unit, basetype)
806 class OneMatrix(Persistence):
808 Classe de stockage d'une matrice de valeurs homogènes par pas.
810 def __init__(self, name="", unit="", basetype = numpy.matrix):
811 Persistence.__init__(self, name, unit, basetype)
813 class OneList(Persistence):
815 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
816 pas utiliser cette classe pour des données numériques homogènes, mais
819 def __init__(self, name="", unit="", basetype = list):
820 Persistence.__init__(self, name, unit, basetype)
823 "Fonction transparente, sans effet sur son argument"
826 class OneNoType(Persistence):
828 Classe de stockage d'un objet sans modification (cast) de type. Attention,
829 selon le véritable type de l'objet stocké à chaque pas, les opérations
830 arithmétiques à base de numpy peuvent être invalides ou donner des
831 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
832 volontairement, et pas du tout par défaut.
834 def __init__(self, name="", unit="", basetype = NoType):
835 Persistence.__init__(self, name, unit, basetype)
837 # ==============================================================================
838 class CompositePersistence(object):
840 Structure de stockage permettant de rassembler plusieurs objets de
843 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
846 def __init__(self, name="", defaults=True):
850 La gestion interne des données est exclusivement basée sur les
851 variables initialisées ici (qui ne sont pas accessibles depuis
852 l'extérieur des objets comme des attributs) :
853 __StoredObjects : objets de type persistence collectés dans cet objet
855 self.__name = str(name)
857 self.__StoredObjects = {}
859 # Definition des objets par defaut
860 # --------------------------------
862 self.__StoredObjects["Informations"] = OneNoType("Informations")
863 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
864 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
865 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
866 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
867 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
868 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
869 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
870 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
871 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
872 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
873 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
874 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
877 def store(self, name=None, value=None, **kwargs):
879 Stockage d'une valeur "value" pour le "step" dans la variable "name".
881 if name is None: raise ValueError("Storable object name is required for storage.")
882 if name not in self.__StoredObjects.keys():
883 raise ValueError("No such name '%s' exists in storable objects."%name)
884 self.__StoredObjects[name].store( value=value, **kwargs )
886 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
888 Ajoute dans les objets stockables un nouvel objet défini par son nom,
889 son type de Persistence et son type de base à chaque pas.
891 if name is None: raise ValueError("Object name is required for adding an object.")
892 if name in self.__StoredObjects.keys():
893 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
895 self.__StoredObjects[name] = persistenceType( name=str(name) )
897 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
899 def get_object(self, name=None ):
901 Renvoie l'objet de type Persistence qui porte le nom demandé.
903 if name is None: raise ValueError("Object name is required for retrieving an object.")
904 if name not in self.__StoredObjects.keys():
905 raise ValueError("No such name '%s' exists in stored objects."%name)
906 return self.__StoredObjects[name]
908 def set_object(self, name=None, objet=None ):
910 Affecte directement un 'objet' qui porte le nom 'name' demandé.
911 Attention, il n'est pas effectué de vérification sur le type, qui doit
912 comporter les méthodes habituelles de Persistence pour que cela
915 if name is None: raise ValueError("Object name is required for setting an object.")
916 if name in self.__StoredObjects.keys():
917 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
918 self.__StoredObjects[name] = objet
920 def del_object(self, name=None ):
922 Supprime un objet de la liste des objets stockables.
924 if name is None: raise ValueError("Object name is required for retrieving an object.")
925 if name not in self.__StoredObjects.keys():
926 raise ValueError("No such name '%s' exists in stored objects."%name)
927 del self.__StoredObjects[name]
929 # ---------------------------------------------------------
930 # Méthodes d'accès de type dictionnaire
931 def __getitem__(self, name=None ):
932 "x.__getitem__(y) <==> x[y]"
933 return self.get_object( name )
935 def __setitem__(self, name=None, objet=None ):
936 "x.__setitem__(i, y) <==> x[i]=y"
937 self.set_object( name, objet )
940 "D.keys() -> list of D's keys"
941 return self.get_stored_objects(hideVoidObjects = False)
944 "D.values() -> list of D's values"
945 return self.__StoredObjects.values()
948 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
949 return self.__StoredObjects.items()
951 # ---------------------------------------------------------
952 def get_stored_objects(self, hideVoidObjects = False):
953 "Renvoie la liste des objets présents"
954 objs = self.__StoredObjects.keys()
959 if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
966 # ---------------------------------------------------------
967 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
969 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
970 et renvoi le nom du fichier
973 if compress == "gzip":
974 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
975 elif compress == "bzip2":
976 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
978 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
980 filename = os.path.abspath( filename )
983 if compress == "gzip":
984 output = gzip.open( filename, 'wb')
985 elif compress == "bzip2":
986 output = bz2.BZ2File( filename, 'wb')
988 output = open( filename, 'wb')
989 pickle.dump(self, output)
992 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
996 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
998 Recharge un objet composite sauvé en fichier
1000 if filename is None:
1001 raise ValueError("A file name if requested to load a composite.")
1003 filename = os.path.abspath( filename )
1005 if mode == "pickle":
1006 if compress == "gzip":
1007 pkl_file = gzip.open( filename, 'rb')
1008 elif compress == "bzip2":
1009 pkl_file = bz2.BZ2File( filename, 'rb')
1011 pkl_file = open(filename, 'rb')
1012 output = pickle.load(pkl_file)
1013 for k in output.keys():
1016 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1020 # ==============================================================================
1021 if __name__ == "__main__":
1022 print('\n AUTODIAGNOSTIC\n')