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
46 "__name", "__unit", "__basetype", "__values", "__tags", "__dynamic",
47 "__g", "__title", "__ltitle", "__pause", "__dataobservers",
50 def __init__(self, name="", unit="", basetype=str):
54 basetype : type de base de l'objet stocké à chaque pas
56 La gestion interne des données est exclusivement basée sur les variables
57 initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
58 objets comme des attributs) :
59 __basetype : le type de base de chaque valeur, sous la forme d'un type
60 permettant l'instanciation ou le casting Python
61 __values : les valeurs de stockage. Par défaut, c'est None
63 self.__name = str(name)
64 self.__unit = str(unit)
66 self.__basetype = basetype
71 self.__dynamic = False
77 self.__dataobservers = []
79 def basetype(self, basetype=None):
81 Renvoie ou met en place le type de base des objets stockés
84 return self.__basetype
86 self.__basetype = basetype
88 def store(self, value=None, **kwargs):
90 Stocke une valeur avec ses informations de filtrage.
92 if value is None: raise ValueError("Value argument required")
94 self.__values.append(copy.copy(self.__basetype(value)))
95 self.__tags.append(kwargs)
97 if self.__dynamic: self.__replots()
98 __step = len(self.__values) - 1
99 for hook, parameters, scheduler in self.__dataobservers:
100 if __step in scheduler:
101 hook( self, parameters )
103 def pop(self, item=None):
105 Retire une valeur enregistrée par son index de stockage. Sans argument,
106 retire le dernier objet enregistre.
110 self.__values.pop(__index)
111 self.__tags.pop(__index)
118 Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un
119 objet numpy, renvoie le shape. Si c'est un entier, un flottant, un
120 complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la
121 longueur. Par défaut, renvoie 1.
123 if len(self.__values) > 0:
124 if self.__basetype in [numpy.matrix, numpy.ndarray, numpy.array, numpy.ravel]:
125 return self.__values[-1].shape
126 elif self.__basetype in [int, float]:
128 elif self.__basetype in [list, dict]:
129 return (len(self.__values[-1]),)
133 raise ValueError("Object has no shape before its first storage")
135 # ---------------------------------------------------------
137 "x.__str__() <==> str(x)"
138 msg = " Index Value Tags\n"
139 for i,v in enumerate(self.__values):
140 msg += " i=%05i %10s %s\n"%(i,v,self.__tags[i])
144 "x.__len__() <==> len(x)"
145 return len(self.__values)
150 def __getitem__(self, index=None ):
151 "x.__getitem__(y) <==> x[y]"
152 return copy.copy(self.__values[index])
154 def count(self, value):
155 "L.count(value) -> integer -- return number of occurrences of value"
156 return self.__values.count(value)
158 def index(self, value, start=0, stop=None):
159 "L.index(value, [start, [stop]]) -> integer -- return first index of value."
160 if stop is None : stop = len(self.__values)
161 return self.__values.index(value, start, stop)
163 # ---------------------------------------------------------
164 def __filteredIndexes(self, **kwargs):
165 "Function interne filtrant les index"
166 __indexOfFilteredItems = range(len(self.__tags))
167 __filteringKwTags = kwargs.keys()
168 if len(__filteringKwTags) > 0:
169 for tagKey in __filteringKwTags:
171 for i in __indexOfFilteredItems:
172 if tagKey in self.__tags[i]:
173 if self.__tags[i][tagKey] == kwargs[tagKey]:
175 elif isinstance(kwargs[tagKey],(list,tuple)) and self.__tags[i][tagKey] in kwargs[tagKey]:
177 __indexOfFilteredItems = __tmp
178 if len(__indexOfFilteredItems) == 0: break
179 return __indexOfFilteredItems
181 # ---------------------------------------------------------
182 def values(self, **kwargs):
183 "D.values() -> list of D's values"
184 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
185 return [self.__values[i] for i in __indexOfFilteredItems]
187 def keys(self, keyword=None , **kwargs):
188 "D.keys() -> list of D's keys"
189 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
191 for i in __indexOfFilteredItems:
192 if keyword in self.__tags[i]:
193 __keys.append( self.__tags[i][keyword] )
195 __keys.append( None )
198 def items(self, keyword=None , **kwargs):
199 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
200 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
202 for i in __indexOfFilteredItems:
203 if keyword in self.__tags[i]:
204 __pairs.append( (self.__tags[i][keyword], self.__values[i]) )
206 __pairs.append( (None, self.__values[i]) )
210 "D.tagkeys() -> list of D's tag keys"
212 for dicotags in self.__tags:
213 __allKeys.extend( list(dicotags.keys()) )
214 __allKeys = sorted(set(__allKeys))
217 # def valueserie(self, item=None, allSteps=True, **kwargs):
218 # if item is not None:
219 # return self.__values[item]
221 # __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
222 # if not allSteps and len(__indexOfFilteredItems) > 0:
223 # return self.__values[__indexOfFilteredItems[0]]
225 # return [self.__values[i] for i in __indexOfFilteredItems]
227 def tagserie(self, item=None, withValues=False, outputTag=None, **kwargs):
228 "D.tagserie() -> list of D's tag serie"
230 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
232 __indexOfFilteredItems = [item,]
234 # Dans le cas où la sortie donne les valeurs d'un "outputTag"
235 if outputTag is not None and isinstance(outputTag,str) :
237 for index in __indexOfFilteredItems:
238 if outputTag in self.__tags[index].keys():
239 outputValues.append( self.__tags[index][outputTag] )
240 outputValues = sorted(set(outputValues))
243 # Dans le cas où la sortie donne les tags satisfaisants aux conditions
246 return [self.__tags[index] for index in __indexOfFilteredItems]
249 for index in __indexOfFilteredItems:
250 allTags.update( self.__tags[index] )
251 allKeys = sorted(allTags.keys())
254 # ---------------------------------------------------------
256 def stepnumber(self):
258 return len(self.__values)
261 def stepserie(self, **kwargs):
262 "Nombre de pas filtrés"
263 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
264 return __indexOfFilteredItems
267 def steplist(self, **kwargs):
268 "Nombre de pas filtrés"
269 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
270 return list(__indexOfFilteredItems)
272 # ---------------------------------------------------------
275 Renvoie la série, contenant à chaque pas, la valeur moyenne des données
276 au pas. Il faut que le type de base soit compatible avec les types
280 return [numpy.mean(item, dtype=mfp).astype('float') for item in self.__values]
282 raise TypeError("Base type is incompatible with numpy")
284 def stds(self, ddof=0):
286 Renvoie la série, contenant à chaque pas, l'écart-type des données
287 au pas. Il faut que le type de base soit compatible avec les types
290 ddof : c'est le nombre de degrés de liberté pour le calcul de
291 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
294 if numpy.version.version >= '1.1.0':
295 return [numpy.array(item).std(ddof=ddof, dtype=mfp).astype('float') for item in self.__values]
297 return [numpy.array(item).std(dtype=mfp).astype('float') for item in self.__values]
299 raise TypeError("Base type is incompatible with numpy")
303 Renvoie la série, contenant à chaque pas, la somme des données au pas.
304 Il faut que le type de base soit compatible avec les types élémentaires
308 return [numpy.array(item).sum() for item in self.__values]
310 raise TypeError("Base type is incompatible with numpy")
314 Renvoie la série, contenant à chaque pas, le minimum des données au pas.
315 Il faut que le type de base soit compatible avec les types élémentaires
319 return [numpy.array(item).min() for item in self.__values]
321 raise TypeError("Base type is incompatible with numpy")
325 Renvoie la série, contenant à chaque pas, la maximum des données au pas.
326 Il faut que le type de base soit compatible avec les types élémentaires
330 return [numpy.array(item).max() for item in self.__values]
332 raise TypeError("Base type is incompatible with numpy")
334 def norms(self, _ord=None):
336 Norm (_ord : voir numpy.linalg.norm)
338 Renvoie la série, contenant à chaque pas, la norme des données au pas.
339 Il faut que le type de base soit compatible avec les types élémentaires
343 return [numpy.linalg.norm(item, _ord) for item in self.__values]
345 raise TypeError("Base type is incompatible with numpy")
347 def maes(self, _predictor=None):
349 Mean Absolute Error (MAE)
350 mae(dX) = 1/n sum(dX_i)
352 Renvoie la série, contenant à chaque pas, la MAE des données au pas.
353 Il faut que le type de base soit compatible avec les types élémentaires
354 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
355 prédicteur est None, sinon c'est appliqué à l'écart entre les données
356 au pas et le prédicteur au même pas.
358 if _predictor is None:
360 return [numpy.mean(numpy.abs(item)) for item in self.__values]
362 raise TypeError("Base type is incompatible with numpy")
364 if len(_predictor) != len(self.__values):
365 raise ValueError("Predictor number of steps is incompatible with the values")
366 for i, item in enumerate(self.__values):
367 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
368 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
370 return [numpy.mean(numpy.abs(numpy.ravel(item) - numpy.ravel(_predictor[i]))) for i, item in enumerate(self.__values)]
372 raise TypeError("Base type is incompatible with numpy")
374 def mses(self, _predictor=None):
376 Mean-Square Error (MSE) ou Mean-Square Deviation (MSD)
377 mse(dX) = 1/n sum(dX_i**2)
379 Renvoie la série, contenant à chaque pas, la MSE des données au pas. Il
380 faut que le type de base soit compatible avec les types élémentaires
381 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
382 prédicteur est None, sinon c'est appliqué à l'écart entre les données
383 au pas et le prédicteur au même pas.
385 if _predictor is None:
387 __n = self.shape()[0]
388 return [(numpy.linalg.norm(item)**2 / __n) for item in self.__values]
390 raise TypeError("Base type is incompatible with numpy")
392 if len(_predictor) != len(self.__values):
393 raise ValueError("Predictor number of steps is incompatible with the values")
394 for i, item in enumerate(self.__values):
395 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
396 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
398 __n = self.shape()[0]
399 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i]))**2 / __n) for i, item in enumerate(self.__values)]
401 raise TypeError("Base type is incompatible with numpy")
403 msds=mses # Mean-Square Deviation (MSD=MSE)
405 def rmses(self, _predictor=None):
407 Root-Mean-Square Error (RMSE) ou Root-Mean-Square Deviation (RMSD)
408 rmse(dX) = sqrt( 1/n sum(dX_i**2) ) = sqrt( mse(dX) )
410 Renvoie la série, contenant à chaque pas, la RMSE des données au pas.
411 Il faut que le type de base soit compatible avec les types élémentaires
412 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
413 prédicteur est None, sinon c'est appliqué à l'écart entre les données
414 au pas et le prédicteur au même pas.
416 if _predictor is None:
418 __n = self.shape()[0]
419 return [(numpy.linalg.norm(item) / math.sqrt(__n)) for item in self.__values]
421 raise TypeError("Base type is incompatible with numpy")
423 if len(_predictor) != len(self.__values):
424 raise ValueError("Predictor number of steps is incompatible with the values")
425 for i, item in enumerate(self.__values):
426 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
427 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
429 __n = self.shape()[0]
430 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i])) / math.sqrt(__n)) for i, item in enumerate(self.__values)]
432 raise TypeError("Base type is incompatible with numpy")
434 rmsds = rmses # Root-Mean-Square Deviation (RMSD=RMSE)
441 geometry = "600x400",
445 "Préparation des plots"
447 # Vérification de la disponibilité du module Gnuplot
449 raise ImportError("The Gnuplot module is required to plot the object.")
451 # Vérification et compléments sur les paramètres d'entrée
453 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
455 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
458 self.__g = Gnuplot.Gnuplot() # persist=1
459 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term)
460 self.__g('set style data lines')
462 self.__g('set autoscale')
463 self.__g('set xlabel "'+str(xlabel)+'"')
464 self.__g('set ylabel "'+str(ylabel)+'"')
466 self.__ltitle = ltitle
477 geometry = "600x400",
484 Renvoie un affichage de la valeur à chaque pas, si elle est compatible
485 avec un affichage Gnuplot (donc essentiellement un vecteur). Si
486 l'argument "step" existe dans la liste des pas de stockage effectués,
487 renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
488 "item" est correct, renvoie l'affichage de la valeur stockée au numéro
489 "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
490 affichage successif de tous les pas.
493 - step : valeur du pas à afficher
494 - item : index de la valeur à afficher
495 - steps : liste unique des pas de l'axe des X, ou None si c'est
496 la numérotation par défaut
497 - title : base du titre général, qui sera automatiquement
498 complétée par la mention du pas
499 - xlabel : label de l'axe des X
500 - ylabel : label de l'axe des Y
501 - ltitle : titre associé au vecteur tracé
502 - geometry : taille en pixels de la fenêtre et position du coin haut
503 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
504 - filename : base de nom de fichier Postscript pour une sauvegarde,
505 qui est automatiquement complétée par le numéro du
506 fichier calculé par incrément simple de compteur
507 - dynamic : effectue un affichage des valeurs à chaque stockage
508 (au-delà du second). La méthode "plots" permet de
509 déclarer l'affichage dynamique, et c'est la méthode
510 "__replots" qui est utilisée pour l'effectuer
511 - persist : booléen indiquant que la fenêtre affichée sera
512 conservée lors du passage au dessin suivant
513 Par défaut, persist = False
514 - pause : booléen indiquant une pause après chaque tracé, et
516 Par défaut, pause = True
518 if not self.__dynamic:
519 self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
521 self.__dynamic = True
522 if len(self.__values) == 0: return 0
524 # Tracé du ou des vecteurs demandés
526 if step is not None and step < len(self.__values):
528 elif item is not None and item < len(self.__values):
531 indexes = indexes + list(range(len(self.__values)))
534 for index in indexes:
535 self.__g('set title "'+str(title)+' (pas '+str(index)+')"')
536 if isinstance(steps,list) or isinstance(steps,numpy.ndarray):
539 Steps = list(range(len(self.__values[index])))
541 self.__g.plot( Gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
545 stepfilename = "%s_%03i.ps"%(filename,i)
546 if os.path.isfile(stepfilename):
547 raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
548 self.__g.hardcopy(filename=stepfilename, color=1)
550 eval(input('Please press return to continue...\n'))
554 Affichage dans le cas du suivi dynamique de la variable
556 if self.__dynamic and len(self.__values) < 2: return 0
558 self.__g('set title "'+str(self.__title))
559 Steps = list(range(len(self.__values)))
560 self.__g.plot( Gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
563 eval(input('Please press return to continue...\n'))
565 # ---------------------------------------------------------
566 # On pourrait aussi utiliser d'autres attributs d'un "array" comme "tofile"
569 Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
570 longueur des pas. Il faut que le type de base soit compatible avec
571 les types élémentaires numpy.
574 return numpy.mean(self.__values, axis=0, dtype=mfp).astype('float')
576 raise TypeError("Base type is incompatible with numpy")
578 def std(self, ddof=0):
580 Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
581 longueur des pas. Il faut que le type de base soit compatible avec
582 les types élémentaires numpy.
584 ddof : c'est le nombre de degrés de liberté pour le calcul de
585 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
588 if numpy.version.version >= '1.1.0':
589 return numpy.asarray(self.__values).std(ddof=ddof,axis=0).astype('float')
591 return numpy.asarray(self.__values).std(axis=0).astype('float')
593 raise TypeError("Base type is incompatible with numpy")
597 Renvoie la somme de toutes les valeurs sans tenir compte de la
598 longueur des pas. Il faut que le type de base soit compatible avec
599 les types élémentaires numpy.
602 return numpy.asarray(self.__values).sum(axis=0)
604 raise TypeError("Base type is incompatible with numpy")
608 Renvoie le minimum de toutes les valeurs sans tenir compte de la
609 longueur des pas. Il faut que le type de base soit compatible avec
610 les types élémentaires numpy.
613 return numpy.asarray(self.__values).min(axis=0)
615 raise TypeError("Base type is incompatible with numpy")
619 Renvoie le maximum de toutes les valeurs sans tenir compte de la
620 longueur des pas. Il faut que le type de base soit compatible avec
621 les types élémentaires numpy.
624 return numpy.asarray(self.__values).max(axis=0)
626 raise TypeError("Base type is incompatible with numpy")
630 Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
631 longueur des pas. Il faut que le type de base soit compatible avec
632 les types élémentaires numpy.
635 return numpy.asarray(self.__values).cumsum(axis=0)
637 raise TypeError("Base type is incompatible with numpy")
645 geometry = "600x400",
651 Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
652 elles sont compatibles avec un affichage Gnuplot (donc essentiellement
653 un vecteur). Si l'argument "step" existe dans la liste des pas de
654 stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
655 "step". Si l'argument "item" est correct, renvoie l'affichage de la
656 valeur stockée au numéro "item".
659 - steps : liste unique des pas de l'axe des X, ou None si c'est
660 la numérotation par défaut
661 - title : base du titre général, qui sera automatiquement
662 complétée par la mention du pas
663 - xlabel : label de l'axe des X
664 - ylabel : label de l'axe des Y
665 - ltitle : titre associé au vecteur tracé
666 - geometry : taille en pixels de la fenêtre et position du coin haut
667 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
668 - filename : nom de fichier Postscript pour une sauvegarde
669 - persist : booléen indiquant que la fenêtre affichée sera
670 conservée lors du passage au dessin suivant
671 Par défaut, persist = False
672 - pause : booléen indiquant une pause après chaque tracé, et
674 Par défaut, pause = True
677 # Vérification de la disponibilité du module Gnuplot
679 raise ImportError("The Gnuplot module is required to plot the object.")
681 # Vérification et compléments sur les paramètres d'entrée
683 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
685 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
688 if isinstance(steps,list) or isinstance(steps, numpy.ndarray):
691 Steps = list(range(len(self.__values[0])))
692 self.__g = Gnuplot.Gnuplot() # persist=1
693 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term)
694 self.__g('set style data lines')
696 self.__g('set autoscale')
697 self.__g('set title "'+str(title) +'"')
698 self.__g('set xlabel "'+str(xlabel)+'"')
699 self.__g('set ylabel "'+str(ylabel)+'"')
701 # Tracé du ou des vecteurs demandés
702 indexes = list(range(len(self.__values)))
703 self.__g.plot( Gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle+" (pas 0)" ) )
704 for index in indexes:
705 self.__g.replot( Gnuplot.Data( Steps, self.__values[index], title=ltitle+" (pas %i)"%index ) )
708 self.__g.hardcopy(filename=filename, color=1)
710 eval(input('Please press return to continue...\n'))
712 # ---------------------------------------------------------
715 Renvoie la série sous la forme d'une unique matrice avec les données au
716 pas rangées par ligne
719 return numpy.asarray(self.__values)
721 raise TypeError("Base type is incompatible with numpy")
725 Renvoie la série sous la forme d'une unique matrice avec les données au
726 pas rangées par colonne
729 return numpy.asarray(self.__values).transpose()
730 # Eqvlt: return numpy.stack([numpy.ravel(sv) for sv in self.__values], axis=1)
732 raise TypeError("Base type is incompatible with numpy")
734 # ---------------------------------------------------------
735 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
737 Association à la variable d'un triplet définissant un observer
739 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
743 # Vérification du Scheduler
744 # -------------------------
746 if isinstance(Scheduler,int): # Considéré comme une fréquence à partir de 0
747 Schedulers = range( 0, maxiter, int(Scheduler) )
748 elif isinstance(Scheduler,range): # Considéré comme un itérateur
749 Schedulers = Scheduler
750 elif isinstance(Scheduler,(list,tuple)): # Considéré comme des index explicites
751 Schedulers = [int(i) for i in Scheduler] # map( long, Scheduler )
752 else: # Dans tous les autres cas, activé par défaut
753 Schedulers = range( 0, maxiter )
755 # Stockage interne de l'observer dans la variable
756 # -----------------------------------------------
757 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
759 def removeDataObserver(self, HookFunction = None, AllObservers = False):
761 Suppression d'un observer nommé sur la variable.
763 On peut donner dans HookFunction la meme fonction que lors de la
764 définition, ou un simple string qui est le nom de la fonction. Si
765 AllObservers est vrai, supprime tous les observers enregistrés.
767 if hasattr(HookFunction,"func_name"):
768 name = str( HookFunction.func_name )
769 elif hasattr(HookFunction,"__name__"):
770 name = str( HookFunction.__name__ )
771 elif isinstance(HookFunction,str):
772 name = str( HookFunction )
778 for [hf, hp, hs] in self.__dataobservers:
780 if name is hf.__name__ or AllObservers: index_to_remove.append( i )
781 index_to_remove.reverse()
782 for i in index_to_remove:
783 self.__dataobservers.pop( i )
784 return len(index_to_remove)
786 def hasDataObserver(self):
787 return bool(len(self.__dataobservers) > 0)
789 # ==============================================================================
790 class SchedulerTrigger(object):
792 Classe générale d'interface de type Scheduler/Trigger
797 simplifiedCombo = None,
799 endTime = int( 1e9 ),
806 # ==============================================================================
807 class OneScalar(Persistence):
809 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
811 Le type de base peut être changé par la méthode "basetype", mais il faut que
812 le nouveau type de base soit compatible avec les types par éléments de
813 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
814 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
815 pour conserver une signification claire des noms.
819 def __init__(self, name="", unit="", basetype = float):
820 Persistence.__init__(self, name, unit, basetype)
822 class OneIndex(Persistence):
824 Classe définissant le stockage d'une valeur unique entière (int) par pas.
828 def __init__(self, name="", unit="", basetype = int):
829 Persistence.__init__(self, name, unit, basetype)
831 class OneVector(Persistence):
833 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
834 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
838 def __init__(self, name="", unit="", basetype = numpy.ravel):
839 Persistence.__init__(self, name, unit, basetype)
841 class OneMatrix(Persistence):
843 Classe de stockage d'une matrice de valeurs homogènes par pas.
847 def __init__(self, name="", unit="", basetype = numpy.matrix):
848 Persistence.__init__(self, name, unit, basetype)
850 class OneList(Persistence):
852 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
853 pas utiliser cette classe pour des données numériques homogènes, mais
858 def __init__(self, name="", unit="", basetype = list):
859 Persistence.__init__(self, name, unit, basetype)
862 "Fonction transparente, sans effet sur son argument"
865 class OneNoType(Persistence):
867 Classe de stockage d'un objet sans modification (cast) de type. Attention,
868 selon le véritable type de l'objet stocké à chaque pas, les opérations
869 arithmétiques à base de numpy peuvent être invalides ou donner des
870 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
871 volontairement, et pas du tout par défaut.
875 def __init__(self, name="", unit="", basetype = NoType):
876 Persistence.__init__(self, name, unit, basetype)
878 # ==============================================================================
879 class CompositePersistence(object):
881 Structure de stockage permettant de rassembler plusieurs objets de
884 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
887 __slots__ = ("__name", "__StoredObjects")
889 def __init__(self, name="", defaults=True):
893 La gestion interne des données est exclusivement basée sur les
894 variables initialisées ici (qui ne sont pas accessibles depuis
895 l'extérieur des objets comme des attributs) :
896 __StoredObjects : objets de type persistence collectés dans cet objet
898 self.__name = str(name)
900 self.__StoredObjects = {}
902 # Definition des objets par defaut
903 # --------------------------------
905 self.__StoredObjects["Informations"] = OneNoType("Informations")
906 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
907 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
908 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
909 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
910 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
911 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
912 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
913 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
914 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
915 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
916 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
917 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
920 def store(self, name=None, value=None, **kwargs):
922 Stockage d'une valeur "value" pour le "step" dans la variable "name".
924 if name is None: raise ValueError("Storable object name is required for storage.")
925 if name not in self.__StoredObjects.keys():
926 raise ValueError("No such name '%s' exists in storable objects."%name)
927 self.__StoredObjects[name].store( value=value, **kwargs )
929 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
931 Ajoute dans les objets stockables un nouvel objet défini par son nom,
932 son type de Persistence et son type de base à chaque pas.
934 if name is None: raise ValueError("Object name is required for adding an object.")
935 if name in self.__StoredObjects.keys():
936 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
938 self.__StoredObjects[name] = persistenceType( name=str(name) )
940 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
942 def get_object(self, name=None ):
944 Renvoie l'objet de type Persistence qui porte le nom demandé.
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 return self.__StoredObjects[name]
951 def set_object(self, name=None, objet=None ):
953 Affecte directement un 'objet' qui porte le nom 'name' demandé.
954 Attention, il n'est pas effectué de vérification sur le type, qui doit
955 comporter les méthodes habituelles de Persistence pour que cela
958 if name is None: raise ValueError("Object name is required for setting an object.")
959 if name in self.__StoredObjects.keys():
960 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
961 self.__StoredObjects[name] = objet
963 def del_object(self, name=None ):
965 Supprime un objet de la liste des objets stockables.
967 if name is None: raise ValueError("Object name is required for retrieving an object.")
968 if name not in self.__StoredObjects.keys():
969 raise ValueError("No such name '%s' exists in stored objects."%name)
970 del self.__StoredObjects[name]
972 # ---------------------------------------------------------
973 # Méthodes d'accès de type dictionnaire
974 def __getitem__(self, name=None ):
975 "x.__getitem__(y) <==> x[y]"
976 return self.get_object( name )
978 def __setitem__(self, name=None, objet=None ):
979 "x.__setitem__(i, y) <==> x[i]=y"
980 self.set_object( name, objet )
983 "D.keys() -> list of D's keys"
984 return self.get_stored_objects(hideVoidObjects = False)
987 "D.values() -> list of D's values"
988 return self.__StoredObjects.values()
991 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
992 return self.__StoredObjects.items()
994 # ---------------------------------------------------------
995 def get_stored_objects(self, hideVoidObjects = False):
996 "Renvoie la liste des objets présents"
997 objs = self.__StoredObjects.keys()
1002 if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
1009 # ---------------------------------------------------------
1010 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
1012 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
1013 et renvoi le nom du fichier
1015 if filename is None:
1016 if compress == "gzip":
1017 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
1018 elif compress == "bzip2":
1019 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
1021 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1023 filename = os.path.abspath( filename )
1025 if mode == "pickle":
1026 if compress == "gzip":
1027 output = gzip.open( filename, 'wb')
1028 elif compress == "bzip2":
1029 output = bz2.BZ2File( filename, 'wb')
1031 output = open( filename, 'wb')
1032 pickle.dump(self, output)
1035 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1039 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1041 Recharge un objet composite sauvé en fichier
1043 if filename is None:
1044 raise ValueError("A file name if requested to load a composite.")
1046 filename = os.path.abspath( filename )
1048 if mode == "pickle":
1049 if compress == "gzip":
1050 pkl_file = gzip.open( filename, 'rb')
1051 elif compress == "bzip2":
1052 pkl_file = bz2.BZ2File( filename, 'rb')
1054 pkl_file = open(filename, 'rb')
1055 output = pickle.load(pkl_file)
1056 for k in output.keys():
1059 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1063 # ==============================================================================
1064 if __name__ == "__main__":
1065 print('\n AUTODIAGNOSTIC\n')