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 powers(self, x2):
336 Renvoie la série, contenant à chaque pas, la puissance "**x2" au pas.
337 Il faut que le type de base soit compatible avec les types élémentaires
341 return [numpy.power(item, x2) for item in self.__values]
343 raise TypeError("Base type is incompatible with numpy")
345 def norms(self, _ord=None):
347 Norm (_ord : voir numpy.linalg.norm)
349 Renvoie la série, contenant à chaque pas, la norme des données au pas.
350 Il faut que le type de base soit compatible avec les types élémentaires
354 return [numpy.linalg.norm(item, _ord) for item in self.__values]
356 raise TypeError("Base type is incompatible with numpy")
358 def maes(self, _predictor=None):
360 Mean Absolute Error (MAE)
361 mae(dX) = 1/n sum(dX_i)
363 Renvoie la série, contenant à chaque pas, la MAE des données au pas.
364 Il faut que le type de base soit compatible avec les types élémentaires
365 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
366 prédicteur est None, sinon c'est appliqué à l'écart entre les données
367 au pas et le prédicteur au même pas.
369 if _predictor is None:
371 return [numpy.mean(numpy.abs(item)) for item in self.__values]
373 raise TypeError("Base type is incompatible with numpy")
375 if len(_predictor) != len(self.__values):
376 raise ValueError("Predictor number of steps is incompatible with the values")
377 for i, item in enumerate(self.__values):
378 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
379 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
381 return [numpy.mean(numpy.abs(numpy.ravel(item) - numpy.ravel(_predictor[i]))) for i, item in enumerate(self.__values)]
383 raise TypeError("Base type is incompatible with numpy")
385 def mses(self, _predictor=None):
387 Mean-Square Error (MSE) ou Mean-Square Deviation (MSD)
388 mse(dX) = 1/n sum(dX_i**2)
390 Renvoie la série, contenant à chaque pas, la MSE des données au pas. Il
391 faut que le type de base soit compatible avec les types élémentaires
392 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
393 prédicteur est None, sinon c'est appliqué à l'écart entre les données
394 au pas et le prédicteur au même pas.
396 if _predictor is None:
398 __n = self.shape()[0]
399 return [(numpy.linalg.norm(item)**2 / __n) for item in self.__values]
401 raise TypeError("Base type is incompatible with numpy")
403 if len(_predictor) != len(self.__values):
404 raise ValueError("Predictor number of steps is incompatible with the values")
405 for i, item in enumerate(self.__values):
406 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
407 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
409 __n = self.shape()[0]
410 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i]))**2 / __n) for i, item in enumerate(self.__values)]
412 raise TypeError("Base type is incompatible with numpy")
414 msds=mses # Mean-Square Deviation (MSD=MSE)
416 def rmses(self, _predictor=None):
418 Root-Mean-Square Error (RMSE) ou Root-Mean-Square Deviation (RMSD)
419 rmse(dX) = sqrt( 1/n sum(dX_i**2) ) = sqrt( mse(dX) )
421 Renvoie la série, contenant à chaque pas, la RMSE des données au pas.
422 Il faut que le type de base soit compatible avec les types élémentaires
423 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
424 prédicteur est None, sinon c'est appliqué à l'écart entre les données
425 au pas et le prédicteur au même pas.
427 if _predictor is None:
429 __n = self.shape()[0]
430 return [(numpy.linalg.norm(item) / math.sqrt(__n)) for item in self.__values]
432 raise TypeError("Base type is incompatible with numpy")
434 if len(_predictor) != len(self.__values):
435 raise ValueError("Predictor number of steps is incompatible with the values")
436 for i, item in enumerate(self.__values):
437 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
438 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
440 __n = self.shape()[0]
441 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i])) / math.sqrt(__n)) for i, item in enumerate(self.__values)]
443 raise TypeError("Base type is incompatible with numpy")
445 rmsds = rmses # Root-Mean-Square Deviation (RMSD=RMSE)
452 geometry = "600x400",
456 "Préparation des plots"
458 # Vérification de la disponibilité du module Gnuplot
460 raise ImportError("The Gnuplot module is required to plot the object.")
462 # Vérification et compléments sur les paramètres d'entrée
464 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
466 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
469 self.__g = Gnuplot.Gnuplot() # persist=1
470 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term)
471 self.__g('set style data lines')
473 self.__g('set autoscale')
474 self.__g('set xlabel "'+str(xlabel)+'"')
475 self.__g('set ylabel "'+str(ylabel)+'"')
477 self.__ltitle = ltitle
488 geometry = "600x400",
495 Renvoie un affichage de la valeur à chaque pas, si elle est compatible
496 avec un affichage Gnuplot (donc essentiellement un vecteur). Si
497 l'argument "step" existe dans la liste des pas de stockage effectués,
498 renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
499 "item" est correct, renvoie l'affichage de la valeur stockée au numéro
500 "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
501 affichage successif de tous les pas.
504 - step : valeur du pas à afficher
505 - item : index de la valeur à afficher
506 - steps : liste unique des pas de l'axe des X, ou None si c'est
507 la numérotation par défaut
508 - title : base du titre général, qui sera automatiquement
509 complétée par la mention du pas
510 - xlabel : label de l'axe des X
511 - ylabel : label de l'axe des Y
512 - ltitle : titre associé au vecteur tracé
513 - geometry : taille en pixels de la fenêtre et position du coin haut
514 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
515 - filename : base de nom de fichier Postscript pour une sauvegarde,
516 qui est automatiquement complétée par le numéro du
517 fichier calculé par incrément simple de compteur
518 - dynamic : effectue un affichage des valeurs à chaque stockage
519 (au-delà du second). La méthode "plots" permet de
520 déclarer l'affichage dynamique, et c'est la méthode
521 "__replots" qui est utilisée pour l'effectuer
522 - persist : booléen indiquant que la fenêtre affichée sera
523 conservée lors du passage au dessin suivant
524 Par défaut, persist = False
525 - pause : booléen indiquant une pause après chaque tracé, et
527 Par défaut, pause = True
529 if not self.__dynamic:
530 self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
532 self.__dynamic = True
533 if len(self.__values) == 0: return 0
535 # Tracé du ou des vecteurs demandés
537 if step is not None and step < len(self.__values):
539 elif item is not None and item < len(self.__values):
542 indexes = indexes + list(range(len(self.__values)))
545 for index in indexes:
546 self.__g('set title "'+str(title)+' (pas '+str(index)+')"')
547 if isinstance(steps,list) or isinstance(steps,numpy.ndarray):
550 Steps = list(range(len(self.__values[index])))
552 self.__g.plot( Gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
556 stepfilename = "%s_%03i.ps"%(filename,i)
557 if os.path.isfile(stepfilename):
558 raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
559 self.__g.hardcopy(filename=stepfilename, color=1)
561 eval(input('Please press return to continue...\n'))
565 Affichage dans le cas du suivi dynamique de la variable
567 if self.__dynamic and len(self.__values) < 2: return 0
569 self.__g('set title "'+str(self.__title))
570 Steps = list(range(len(self.__values)))
571 self.__g.plot( Gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
574 eval(input('Please press return to continue...\n'))
576 # ---------------------------------------------------------
577 # On pourrait aussi utiliser d'autres attributs d'un "array" comme "tofile"
580 Renvoie la moyenne sur 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.
585 return numpy.mean(self.__values, axis=0, dtype=mfp).astype('float')
587 raise TypeError("Base type is incompatible with numpy")
589 def std(self, ddof=0):
591 Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
592 longueur des pas. Il faut que le type de base soit compatible avec
593 les types élémentaires numpy.
595 ddof : c'est le nombre de degrés de liberté pour le calcul de
596 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
599 if numpy.version.version >= '1.1.0':
600 return numpy.asarray(self.__values).std(ddof=ddof,axis=0).astype('float')
602 return numpy.asarray(self.__values).std(axis=0).astype('float')
604 raise TypeError("Base type is incompatible with numpy")
608 Renvoie la somme 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).sum(axis=0)
615 raise TypeError("Base type is incompatible with numpy")
619 Renvoie le minimum 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).min(axis=0)
626 raise TypeError("Base type is incompatible with numpy")
630 Renvoie le maximum 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).max(axis=0)
637 raise TypeError("Base type is incompatible with numpy")
641 Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
642 longueur des pas. Il faut que le type de base soit compatible avec
643 les types élémentaires numpy.
646 return numpy.asarray(self.__values).cumsum(axis=0)
648 raise TypeError("Base type is incompatible with numpy")
656 geometry = "600x400",
662 Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
663 elles sont compatibles avec un affichage Gnuplot (donc essentiellement
664 un vecteur). Si l'argument "step" existe dans la liste des pas de
665 stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
666 "step". Si l'argument "item" est correct, renvoie l'affichage de la
667 valeur stockée au numéro "item".
670 - steps : liste unique des pas de l'axe des X, ou None si c'est
671 la numérotation par défaut
672 - title : base du titre général, qui sera automatiquement
673 complétée par la mention du pas
674 - xlabel : label de l'axe des X
675 - ylabel : label de l'axe des Y
676 - ltitle : titre associé au vecteur tracé
677 - geometry : taille en pixels de la fenêtre et position du coin haut
678 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
679 - filename : nom de fichier Postscript pour une sauvegarde
680 - persist : booléen indiquant que la fenêtre affichée sera
681 conservée lors du passage au dessin suivant
682 Par défaut, persist = False
683 - pause : booléen indiquant une pause après chaque tracé, et
685 Par défaut, pause = True
688 # Vérification de la disponibilité du module Gnuplot
690 raise ImportError("The Gnuplot module is required to plot the object.")
692 # Vérification et compléments sur les paramètres d'entrée
694 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist -geometry '+geometry
696 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -geometry '+geometry
699 if isinstance(steps,list) or isinstance(steps, numpy.ndarray):
702 Steps = list(range(len(self.__values[0])))
703 self.__g = Gnuplot.Gnuplot() # persist=1
704 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term)
705 self.__g('set style data lines')
707 self.__g('set autoscale')
708 self.__g('set title "'+str(title) +'"')
709 self.__g('set xlabel "'+str(xlabel)+'"')
710 self.__g('set ylabel "'+str(ylabel)+'"')
712 # Tracé du ou des vecteurs demandés
713 indexes = list(range(len(self.__values)))
714 self.__g.plot( Gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle+" (pas 0)" ) )
715 for index in indexes:
716 self.__g.replot( Gnuplot.Data( Steps, self.__values[index], title=ltitle+" (pas %i)"%index ) )
719 self.__g.hardcopy(filename=filename, color=1)
721 eval(input('Please press return to continue...\n'))
723 # ---------------------------------------------------------
726 Renvoie la série sous la forme d'une unique matrice avec les données au
727 pas rangées par ligne
730 return numpy.asarray(self.__values)
732 raise TypeError("Base type is incompatible with numpy")
736 Renvoie la série sous la forme d'une unique matrice avec les données au
737 pas rangées par colonne
740 return numpy.asarray(self.__values).transpose()
741 # Eqvlt: return numpy.stack([numpy.ravel(sv) for sv in self.__values], axis=1)
743 raise TypeError("Base type is incompatible with numpy")
745 # ---------------------------------------------------------
746 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
748 Association à la variable d'un triplet définissant un observer
750 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
754 # Vérification du Scheduler
755 # -------------------------
757 if isinstance(Scheduler,int): # Considéré comme une fréquence à partir de 0
758 Schedulers = range( 0, maxiter, int(Scheduler) )
759 elif isinstance(Scheduler,range): # Considéré comme un itérateur
760 Schedulers = Scheduler
761 elif isinstance(Scheduler,(list,tuple)): # Considéré comme des index explicites
762 Schedulers = [int(i) for i in Scheduler] # map( long, Scheduler )
763 else: # Dans tous les autres cas, activé par défaut
764 Schedulers = range( 0, maxiter )
766 # Stockage interne de l'observer dans la variable
767 # -----------------------------------------------
768 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
770 def removeDataObserver(self, HookFunction = None, AllObservers = False):
772 Suppression d'un observer nommé sur la variable.
774 On peut donner dans HookFunction la meme fonction que lors de la
775 définition, ou un simple string qui est le nom de la fonction. Si
776 AllObservers est vrai, supprime tous les observers enregistrés.
778 if hasattr(HookFunction,"func_name"):
779 name = str( HookFunction.func_name )
780 elif hasattr(HookFunction,"__name__"):
781 name = str( HookFunction.__name__ )
782 elif isinstance(HookFunction,str):
783 name = str( HookFunction )
789 for [hf, hp, hs] in self.__dataobservers:
791 if name is hf.__name__ or AllObservers: index_to_remove.append( i )
792 index_to_remove.reverse()
793 for i in index_to_remove:
794 self.__dataobservers.pop( i )
795 return len(index_to_remove)
797 def hasDataObserver(self):
798 return bool(len(self.__dataobservers) > 0)
800 # ==============================================================================
801 class SchedulerTrigger(object):
803 Classe générale d'interface de type Scheduler/Trigger
808 simplifiedCombo = None,
810 endTime = int( 1e9 ),
817 # ==============================================================================
818 class OneScalar(Persistence):
820 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
822 Le type de base peut être changé par la méthode "basetype", mais il faut que
823 le nouveau type de base soit compatible avec les types par éléments de
824 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
825 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
826 pour conserver une signification claire des noms.
830 def __init__(self, name="", unit="", basetype = float):
831 Persistence.__init__(self, name, unit, basetype)
833 class OneIndex(Persistence):
835 Classe définissant le stockage d'une valeur unique entière (int) par pas.
839 def __init__(self, name="", unit="", basetype = int):
840 Persistence.__init__(self, name, unit, basetype)
842 class OneVector(Persistence):
844 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
845 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
849 def __init__(self, name="", unit="", basetype = numpy.ravel):
850 Persistence.__init__(self, name, unit, basetype)
852 class OneMatrice(Persistence):
854 Classe de stockage d'une matrice de valeurs homogènes par pas.
858 def __init__(self, name="", unit="", basetype = numpy.array):
859 Persistence.__init__(self, name, unit, basetype)
861 class OneMatrix(Persistence):
863 Classe de stockage d'une matrice de valeurs homogènes par pas.
867 def __init__(self, name="", unit="", basetype = numpy.matrix):
868 Persistence.__init__(self, name, unit, basetype)
870 class OneList(Persistence):
872 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
873 pas utiliser cette classe pour des données numériques homogènes, mais
878 def __init__(self, name="", unit="", basetype = list):
879 Persistence.__init__(self, name, unit, basetype)
882 "Fonction transparente, sans effet sur son argument"
885 class OneNoType(Persistence):
887 Classe de stockage d'un objet sans modification (cast) de type. Attention,
888 selon le véritable type de l'objet stocké à chaque pas, les opérations
889 arithmétiques à base de numpy peuvent être invalides ou donner des
890 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
891 volontairement, et pas du tout par défaut.
895 def __init__(self, name="", unit="", basetype = NoType):
896 Persistence.__init__(self, name, unit, basetype)
898 # ==============================================================================
899 class CompositePersistence(object):
901 Structure de stockage permettant de rassembler plusieurs objets de
904 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
907 __slots__ = ("__name", "__StoredObjects")
909 def __init__(self, name="", defaults=True):
913 La gestion interne des données est exclusivement basée sur les
914 variables initialisées ici (qui ne sont pas accessibles depuis
915 l'extérieur des objets comme des attributs) :
916 __StoredObjects : objets de type persistence collectés dans cet objet
918 self.__name = str(name)
920 self.__StoredObjects = {}
922 # Definition des objets par defaut
923 # --------------------------------
925 self.__StoredObjects["Informations"] = OneNoType("Informations")
926 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
927 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
928 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
929 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
930 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
931 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
932 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
933 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
934 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
935 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
936 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
937 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
940 def store(self, name=None, value=None, **kwargs):
942 Stockage d'une valeur "value" pour le "step" dans la variable "name".
944 if name is None: raise ValueError("Storable object name is required for storage.")
945 if name not in self.__StoredObjects.keys():
946 raise ValueError("No such name '%s' exists in storable objects."%name)
947 self.__StoredObjects[name].store( value=value, **kwargs )
949 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
951 Ajoute dans les objets stockables un nouvel objet défini par son nom,
952 son type de Persistence et son type de base à chaque pas.
954 if name is None: raise ValueError("Object name is required for adding an object.")
955 if name in self.__StoredObjects.keys():
956 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
958 self.__StoredObjects[name] = persistenceType( name=str(name) )
960 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
962 def get_object(self, name=None ):
964 Renvoie l'objet de type Persistence qui porte le nom demandé.
966 if name is None: raise ValueError("Object name is required for retrieving an object.")
967 if name not in self.__StoredObjects.keys():
968 raise ValueError("No such name '%s' exists in stored objects."%name)
969 return self.__StoredObjects[name]
971 def set_object(self, name=None, objet=None ):
973 Affecte directement un 'objet' qui porte le nom 'name' demandé.
974 Attention, il n'est pas effectué de vérification sur le type, qui doit
975 comporter les méthodes habituelles de Persistence pour que cela
978 if name is None: raise ValueError("Object name is required for setting an object.")
979 if name in self.__StoredObjects.keys():
980 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
981 self.__StoredObjects[name] = objet
983 def del_object(self, name=None ):
985 Supprime un objet de la liste des objets stockables.
987 if name is None: raise ValueError("Object name is required for retrieving an object.")
988 if name not in self.__StoredObjects.keys():
989 raise ValueError("No such name '%s' exists in stored objects."%name)
990 del self.__StoredObjects[name]
992 # ---------------------------------------------------------
993 # Méthodes d'accès de type dictionnaire
994 def __getitem__(self, name=None ):
995 "x.__getitem__(y) <==> x[y]"
996 return self.get_object( name )
998 def __setitem__(self, name=None, objet=None ):
999 "x.__setitem__(i, y) <==> x[i]=y"
1000 self.set_object( name, objet )
1003 "D.keys() -> list of D's keys"
1004 return self.get_stored_objects(hideVoidObjects = False)
1007 "D.values() -> list of D's values"
1008 return self.__StoredObjects.values()
1011 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
1012 return self.__StoredObjects.items()
1014 # ---------------------------------------------------------
1015 def get_stored_objects(self, hideVoidObjects = False):
1016 "Renvoie la liste des objets présents"
1017 objs = self.__StoredObjects.keys()
1022 if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
1029 # ---------------------------------------------------------
1030 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
1032 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
1033 et renvoi le nom du fichier
1035 if filename is None:
1036 if compress == "gzip":
1037 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
1038 elif compress == "bzip2":
1039 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
1041 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1043 filename = os.path.abspath( filename )
1045 if mode == "pickle":
1046 if compress == "gzip":
1047 output = gzip.open( filename, 'wb')
1048 elif compress == "bzip2":
1049 output = bz2.BZ2File( filename, 'wb')
1051 output = open( filename, 'wb')
1052 pickle.dump(self, output)
1055 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1059 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1061 Recharge un objet composite sauvé en fichier
1063 if filename is None:
1064 raise ValueError("A file name if requested to load a composite.")
1066 filename = os.path.abspath( filename )
1068 if mode == "pickle":
1069 if compress == "gzip":
1070 pkl_file = gzip.open( filename, 'rb')
1071 elif compress == "bzip2":
1072 pkl_file = bz2.BZ2File( filename, 'rb')
1074 pkl_file = open(filename, 'rb')
1075 output = pickle.load(pkl_file)
1076 for k in output.keys():
1079 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1083 # ==============================================================================
1084 if __name__ == "__main__":
1085 print("\n AUTODIAGNOSTIC\n")