1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008-2023 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 # ---------------------------------------------------------
710 Renvoie la série sous la forme d'une unique matrice avec les données au
711 pas rangées par ligne
714 return numpy.asarray(self.__values)
716 raise TypeError("Base type is incompatible with numpy")
720 Renvoie la série sous la forme d'une unique matrice avec les données au
721 pas rangées par colonne
724 return numpy.asarray(self.__values).transpose()
726 raise TypeError("Base type is incompatible with numpy")
728 # ---------------------------------------------------------
729 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
731 Association à la variable d'un triplet définissant un observer
733 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
737 # Vérification du Scheduler
738 # -------------------------
740 if isinstance(Scheduler,int): # Considéré comme une fréquence à partir de 0
741 Schedulers = range( 0, maxiter, int(Scheduler) )
742 elif isinstance(Scheduler,range): # Considéré comme un itérateur
743 Schedulers = Scheduler
744 elif isinstance(Scheduler,(list,tuple)): # Considéré comme des index explicites
745 Schedulers = [int(i) for i in Scheduler] # map( long, Scheduler )
746 else: # Dans tous les autres cas, activé par défaut
747 Schedulers = range( 0, maxiter )
749 # Stockage interne de l'observer dans la variable
750 # -----------------------------------------------
751 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
753 def removeDataObserver(self, HookFunction = None, AllObservers = False):
755 Suppression d'un observer nommé sur la variable.
757 On peut donner dans HookFunction la meme fonction que lors de la
758 définition, ou un simple string qui est le nom de la fonction. Si
759 AllObservers est vrai, supprime tous les observers enregistrés.
761 if hasattr(HookFunction,"func_name"):
762 name = str( HookFunction.func_name )
763 elif hasattr(HookFunction,"__name__"):
764 name = str( HookFunction.__name__ )
765 elif isinstance(HookFunction,str):
766 name = str( HookFunction )
772 for [hf, hp, hs] in self.__dataobservers:
774 if name is hf.__name__ or AllObservers: index_to_remove.append( i )
775 index_to_remove.reverse()
776 for i in index_to_remove:
777 self.__dataobservers.pop( i )
778 return len(index_to_remove)
780 def hasDataObserver(self):
781 return bool(len(self.__dataobservers) > 0)
783 # ==============================================================================
784 class SchedulerTrigger(object):
786 Classe générale d'interface de type Scheduler/Trigger
789 simplifiedCombo = None,
791 endTime = int( 1e9 ),
798 # ==============================================================================
799 class OneScalar(Persistence):
801 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
803 Le type de base peut être changé par la méthode "basetype", mais il faut que
804 le nouveau type de base soit compatible avec les types par éléments de
805 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
806 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
807 pour conserver une signification claire des noms.
809 def __init__(self, name="", unit="", basetype = float):
810 Persistence.__init__(self, name, unit, basetype)
812 class OneIndex(Persistence):
814 Classe définissant le stockage d'une valeur unique entière (int) par pas.
816 def __init__(self, name="", unit="", basetype = int):
817 Persistence.__init__(self, name, unit, basetype)
819 class OneVector(Persistence):
821 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
822 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
824 def __init__(self, name="", unit="", basetype = numpy.ravel):
825 Persistence.__init__(self, name, unit, basetype)
827 class OneMatrix(Persistence):
829 Classe de stockage d'une matrice de valeurs homogènes par pas.
831 def __init__(self, name="", unit="", basetype = numpy.matrix):
832 Persistence.__init__(self, name, unit, basetype)
834 class OneList(Persistence):
836 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
837 pas utiliser cette classe pour des données numériques homogènes, mais
840 def __init__(self, name="", unit="", basetype = list):
841 Persistence.__init__(self, name, unit, basetype)
844 "Fonction transparente, sans effet sur son argument"
847 class OneNoType(Persistence):
849 Classe de stockage d'un objet sans modification (cast) de type. Attention,
850 selon le véritable type de l'objet stocké à chaque pas, les opérations
851 arithmétiques à base de numpy peuvent être invalides ou donner des
852 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
853 volontairement, et pas du tout par défaut.
855 def __init__(self, name="", unit="", basetype = NoType):
856 Persistence.__init__(self, name, unit, basetype)
858 # ==============================================================================
859 class CompositePersistence(object):
861 Structure de stockage permettant de rassembler plusieurs objets de
864 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
867 def __init__(self, name="", defaults=True):
871 La gestion interne des données est exclusivement basée sur les
872 variables initialisées ici (qui ne sont pas accessibles depuis
873 l'extérieur des objets comme des attributs) :
874 __StoredObjects : objets de type persistence collectés dans cet objet
876 self.__name = str(name)
878 self.__StoredObjects = {}
880 # Definition des objets par defaut
881 # --------------------------------
883 self.__StoredObjects["Informations"] = OneNoType("Informations")
884 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
885 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
886 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
887 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
888 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
889 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
890 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
891 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
892 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
893 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
894 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
895 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
898 def store(self, name=None, value=None, **kwargs):
900 Stockage d'une valeur "value" pour le "step" dans la variable "name".
902 if name is None: raise ValueError("Storable object name is required for storage.")
903 if name not in self.__StoredObjects.keys():
904 raise ValueError("No such name '%s' exists in storable objects."%name)
905 self.__StoredObjects[name].store( value=value, **kwargs )
907 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
909 Ajoute dans les objets stockables un nouvel objet défini par son nom,
910 son type de Persistence et son type de base à chaque pas.
912 if name is None: raise ValueError("Object name is required for adding an object.")
913 if name in self.__StoredObjects.keys():
914 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
916 self.__StoredObjects[name] = persistenceType( name=str(name) )
918 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
920 def get_object(self, name=None ):
922 Renvoie l'objet de type Persistence qui porte le nom demandé.
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 return self.__StoredObjects[name]
929 def set_object(self, name=None, objet=None ):
931 Affecte directement un 'objet' qui porte le nom 'name' demandé.
932 Attention, il n'est pas effectué de vérification sur le type, qui doit
933 comporter les méthodes habituelles de Persistence pour que cela
936 if name is None: raise ValueError("Object name is required for setting an object.")
937 if name in self.__StoredObjects.keys():
938 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
939 self.__StoredObjects[name] = objet
941 def del_object(self, name=None ):
943 Supprime un objet de la liste des objets stockables.
945 if name is None: raise ValueError("Object name is required for retrieving an object.")
946 if name not in self.__StoredObjects.keys():
947 raise ValueError("No such name '%s' exists in stored objects."%name)
948 del self.__StoredObjects[name]
950 # ---------------------------------------------------------
951 # Méthodes d'accès de type dictionnaire
952 def __getitem__(self, name=None ):
953 "x.__getitem__(y) <==> x[y]"
954 return self.get_object( name )
956 def __setitem__(self, name=None, objet=None ):
957 "x.__setitem__(i, y) <==> x[i]=y"
958 self.set_object( name, objet )
961 "D.keys() -> list of D's keys"
962 return self.get_stored_objects(hideVoidObjects = False)
965 "D.values() -> list of D's values"
966 return self.__StoredObjects.values()
969 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
970 return self.__StoredObjects.items()
972 # ---------------------------------------------------------
973 def get_stored_objects(self, hideVoidObjects = False):
974 "Renvoie la liste des objets présents"
975 objs = self.__StoredObjects.keys()
980 if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
987 # ---------------------------------------------------------
988 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
990 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
991 et renvoi le nom du fichier
994 if compress == "gzip":
995 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
996 elif compress == "bzip2":
997 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
999 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1001 filename = os.path.abspath( filename )
1003 if mode == "pickle":
1004 if compress == "gzip":
1005 output = gzip.open( filename, 'wb')
1006 elif compress == "bzip2":
1007 output = bz2.BZ2File( filename, 'wb')
1009 output = open( filename, 'wb')
1010 pickle.dump(self, output)
1013 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1017 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1019 Recharge un objet composite sauvé en fichier
1021 if filename is None:
1022 raise ValueError("A file name if requested to load a composite.")
1024 filename = os.path.abspath( filename )
1026 if mode == "pickle":
1027 if compress == "gzip":
1028 pkl_file = gzip.open( filename, 'rb')
1029 elif compress == "bzip2":
1030 pkl_file = bz2.BZ2File( filename, 'rb')
1032 pkl_file = open(filename, 'rb')
1033 output = pickle.load(pkl_file)
1034 for k in output.keys():
1037 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1041 # ==============================================================================
1042 if __name__ == "__main__":
1043 print('\n AUTODIAGNOSTIC\n')