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()
725 # Eqvlt: return numpy.stack([numpy.ravel(sv) for sv in self.__values], axis=1)
727 raise TypeError("Base type is incompatible with numpy")
729 # ---------------------------------------------------------
730 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
732 Association à la variable d'un triplet définissant un observer
734 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
738 # Vérification du Scheduler
739 # -------------------------
741 if isinstance(Scheduler,int): # Considéré comme une fréquence à partir de 0
742 Schedulers = range( 0, maxiter, int(Scheduler) )
743 elif isinstance(Scheduler,range): # Considéré comme un itérateur
744 Schedulers = Scheduler
745 elif isinstance(Scheduler,(list,tuple)): # Considéré comme des index explicites
746 Schedulers = [int(i) for i in Scheduler] # map( long, Scheduler )
747 else: # Dans tous les autres cas, activé par défaut
748 Schedulers = range( 0, maxiter )
750 # Stockage interne de l'observer dans la variable
751 # -----------------------------------------------
752 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
754 def removeDataObserver(self, HookFunction = None, AllObservers = False):
756 Suppression d'un observer nommé sur la variable.
758 On peut donner dans HookFunction la meme fonction que lors de la
759 définition, ou un simple string qui est le nom de la fonction. Si
760 AllObservers est vrai, supprime tous les observers enregistrés.
762 if hasattr(HookFunction,"func_name"):
763 name = str( HookFunction.func_name )
764 elif hasattr(HookFunction,"__name__"):
765 name = str( HookFunction.__name__ )
766 elif isinstance(HookFunction,str):
767 name = str( HookFunction )
773 for [hf, hp, hs] in self.__dataobservers:
775 if name is hf.__name__ or AllObservers: index_to_remove.append( i )
776 index_to_remove.reverse()
777 for i in index_to_remove:
778 self.__dataobservers.pop( i )
779 return len(index_to_remove)
781 def hasDataObserver(self):
782 return bool(len(self.__dataobservers) > 0)
784 # ==============================================================================
785 class SchedulerTrigger(object):
787 Classe générale d'interface de type Scheduler/Trigger
790 simplifiedCombo = None,
792 endTime = int( 1e9 ),
799 # ==============================================================================
800 class OneScalar(Persistence):
802 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
804 Le type de base peut être changé par la méthode "basetype", mais il faut que
805 le nouveau type de base soit compatible avec les types par éléments de
806 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
807 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
808 pour conserver une signification claire des noms.
810 def __init__(self, name="", unit="", basetype = float):
811 Persistence.__init__(self, name, unit, basetype)
813 class OneIndex(Persistence):
815 Classe définissant le stockage d'une valeur unique entière (int) par pas.
817 def __init__(self, name="", unit="", basetype = int):
818 Persistence.__init__(self, name, unit, basetype)
820 class OneVector(Persistence):
822 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
823 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
825 def __init__(self, name="", unit="", basetype = numpy.ravel):
826 Persistence.__init__(self, name, unit, basetype)
828 class OneMatrix(Persistence):
830 Classe de stockage d'une matrice de valeurs homogènes par pas.
832 def __init__(self, name="", unit="", basetype = numpy.matrix):
833 Persistence.__init__(self, name, unit, basetype)
835 class OneList(Persistence):
837 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
838 pas utiliser cette classe pour des données numériques homogènes, mais
841 def __init__(self, name="", unit="", basetype = list):
842 Persistence.__init__(self, name, unit, basetype)
845 "Fonction transparente, sans effet sur son argument"
848 class OneNoType(Persistence):
850 Classe de stockage d'un objet sans modification (cast) de type. Attention,
851 selon le véritable type de l'objet stocké à chaque pas, les opérations
852 arithmétiques à base de numpy peuvent être invalides ou donner des
853 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
854 volontairement, et pas du tout par défaut.
856 def __init__(self, name="", unit="", basetype = NoType):
857 Persistence.__init__(self, name, unit, basetype)
859 # ==============================================================================
860 class CompositePersistence(object):
862 Structure de stockage permettant de rassembler plusieurs objets de
865 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
868 def __init__(self, name="", defaults=True):
872 La gestion interne des données est exclusivement basée sur les
873 variables initialisées ici (qui ne sont pas accessibles depuis
874 l'extérieur des objets comme des attributs) :
875 __StoredObjects : objets de type persistence collectés dans cet objet
877 self.__name = str(name)
879 self.__StoredObjects = {}
881 # Definition des objets par defaut
882 # --------------------------------
884 self.__StoredObjects["Informations"] = OneNoType("Informations")
885 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
886 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
887 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
888 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
889 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
890 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
891 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
892 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
893 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
894 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
895 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
896 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
899 def store(self, name=None, value=None, **kwargs):
901 Stockage d'une valeur "value" pour le "step" dans la variable "name".
903 if name is None: raise ValueError("Storable object name is required for storage.")
904 if name not in self.__StoredObjects.keys():
905 raise ValueError("No such name '%s' exists in storable objects."%name)
906 self.__StoredObjects[name].store( value=value, **kwargs )
908 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
910 Ajoute dans les objets stockables un nouvel objet défini par son nom,
911 son type de Persistence et son type de base à chaque pas.
913 if name is None: raise ValueError("Object name is required for adding an object.")
914 if name in self.__StoredObjects.keys():
915 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
917 self.__StoredObjects[name] = persistenceType( name=str(name) )
919 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
921 def get_object(self, name=None ):
923 Renvoie l'objet de type Persistence qui porte le nom demandé.
925 if name is None: raise ValueError("Object name is required for retrieving an object.")
926 if name not in self.__StoredObjects.keys():
927 raise ValueError("No such name '%s' exists in stored objects."%name)
928 return self.__StoredObjects[name]
930 def set_object(self, name=None, objet=None ):
932 Affecte directement un 'objet' qui porte le nom 'name' demandé.
933 Attention, il n'est pas effectué de vérification sur le type, qui doit
934 comporter les méthodes habituelles de Persistence pour que cela
937 if name is None: raise ValueError("Object name is required for setting an object.")
938 if name in self.__StoredObjects.keys():
939 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
940 self.__StoredObjects[name] = objet
942 def del_object(self, name=None ):
944 Supprime un objet de la liste des objets stockables.
946 if name is None: raise ValueError("Object name is required for retrieving an object.")
947 if name not in self.__StoredObjects.keys():
948 raise ValueError("No such name '%s' exists in stored objects."%name)
949 del self.__StoredObjects[name]
951 # ---------------------------------------------------------
952 # Méthodes d'accès de type dictionnaire
953 def __getitem__(self, name=None ):
954 "x.__getitem__(y) <==> x[y]"
955 return self.get_object( name )
957 def __setitem__(self, name=None, objet=None ):
958 "x.__setitem__(i, y) <==> x[i]=y"
959 self.set_object( name, objet )
962 "D.keys() -> list of D's keys"
963 return self.get_stored_objects(hideVoidObjects = False)
966 "D.values() -> list of D's values"
967 return self.__StoredObjects.values()
970 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
971 return self.__StoredObjects.items()
973 # ---------------------------------------------------------
974 def get_stored_objects(self, hideVoidObjects = False):
975 "Renvoie la liste des objets présents"
976 objs = self.__StoredObjects.keys()
981 if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
988 # ---------------------------------------------------------
989 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
991 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
992 et renvoi le nom du fichier
995 if compress == "gzip":
996 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
997 elif compress == "bzip2":
998 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
1000 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1002 filename = os.path.abspath( filename )
1004 if mode == "pickle":
1005 if compress == "gzip":
1006 output = gzip.open( filename, 'wb')
1007 elif compress == "bzip2":
1008 output = bz2.BZ2File( filename, 'wb')
1010 output = open( filename, 'wb')
1011 pickle.dump(self, output)
1014 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1018 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1020 Recharge un objet composite sauvé en fichier
1022 if filename is None:
1023 raise ValueError("A file name if requested to load a composite.")
1025 filename = os.path.abspath( filename )
1027 if mode == "pickle":
1028 if compress == "gzip":
1029 pkl_file = gzip.open( filename, 'rb')
1030 elif compress == "bzip2":
1031 pkl_file = bz2.BZ2File( filename, 'rb')
1033 pkl_file = open(filename, 'rb')
1034 output = pickle.load(pkl_file)
1035 for k in output.keys():
1038 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1042 # ==============================================================================
1043 if __name__ == "__main__":
1044 print('\n AUTODIAGNOSTIC\n')