1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2008-2024 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() # noqa: E702,E203
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.
93 raise ValueError("Value argument required")
95 self.__values.append(copy.copy(self.__basetype(value)))
96 self.__tags.append(kwargs)
100 __step = len(self.__values) - 1
101 for hook, parameters, scheduler in self.__dataobservers:
102 if __step in scheduler:
103 hook( self, parameters )
105 def pop(self, item=None):
107 Retire une valeur enregistrée par son index de stockage. Sans argument,
108 retire le dernier objet enregistre.
112 self.__values.pop(__index)
113 self.__tags.pop(__index)
120 Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un
121 objet numpy, renvoie le shape. Si c'est un entier, un flottant, un
122 complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la
123 longueur. Par défaut, renvoie 1.
125 if len(self.__values) > 0:
126 if self.__basetype in [numpy.matrix, numpy.ndarray, numpy.array, numpy.ravel]:
127 return self.__values[-1].shape
128 elif self.__basetype in [int, float]:
130 elif self.__basetype in [list, dict]:
131 return (len(self.__values[-1]),)
135 raise ValueError("Object has no shape before its first storage")
137 # ---------------------------------------------------------
139 "x.__str__() <==> str(x)"
140 msg = " Index Value Tags\n"
141 for iv, vv in enumerate(self.__values):
142 msg += " i=%05i %10s %s\n"%(iv, vv, self.__tags[iv])
146 "x.__len__() <==> len(x)"
147 return len(self.__values)
152 def __getitem__(self, index=None ):
153 "x.__getitem__(y) <==> x[y]"
154 return copy.copy(self.__values[index])
156 def count(self, value):
157 "L.count(value) -> integer -- return number of occurrences of value"
158 return self.__values.count(value)
160 def index(self, value, start=0, stop=None):
161 "L.index(value, [start, [stop]]) -> integer -- return first index of value."
163 stop = len(self.__values)
164 return self.__values.index(value, start, stop)
166 # ---------------------------------------------------------
167 def __filteredIndexes(self, **kwargs):
168 "Function interne filtrant les index"
169 __indexOfFilteredItems = range(len(self.__tags))
170 __filteringKwTags = kwargs.keys()
171 if len(__filteringKwTags) > 0:
172 for tagKey in __filteringKwTags:
174 for i in __indexOfFilteredItems:
175 if tagKey in self.__tags[i]:
176 if self.__tags[i][tagKey] == kwargs[tagKey]:
178 elif isinstance(kwargs[tagKey], (list, tuple)) and self.__tags[i][tagKey] in kwargs[tagKey]:
180 __indexOfFilteredItems = __tmp
181 if len(__indexOfFilteredItems) == 0:
183 return __indexOfFilteredItems
185 # ---------------------------------------------------------
186 def values(self, **kwargs):
187 "D.values() -> list of D's values"
188 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
189 return [self.__values[i] for i in __indexOfFilteredItems]
191 def keys(self, keyword=None, **kwargs):
192 "D.keys() -> list of D's keys"
193 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
195 for i in __indexOfFilteredItems:
196 if keyword in self.__tags[i]:
197 __keys.append( self.__tags[i][keyword] )
199 __keys.append( None )
202 def items(self, keyword=None, **kwargs):
203 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
204 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
206 for i in __indexOfFilteredItems:
207 if keyword in self.__tags[i]:
208 __pairs.append( (self.__tags[i][keyword], self.__values[i]) )
210 __pairs.append( (None, self.__values[i]) )
214 "D.tagkeys() -> list of D's tag keys"
216 for dicotags in self.__tags:
217 __allKeys.extend( list(dicotags.keys()) )
218 __allKeys = sorted(set(__allKeys))
221 # def valueserie(self, item=None, allSteps=True, **kwargs):
222 # if item is not None:
223 # return self.__values[item]
225 # __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
226 # if not allSteps and len(__indexOfFilteredItems) > 0:
227 # return self.__values[__indexOfFilteredItems[0]]
229 # return [self.__values[i] for i in __indexOfFilteredItems]
231 def tagserie(self, item=None, withValues=False, outputTag=None, **kwargs):
232 "D.tagserie() -> list of D's tag serie"
234 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
236 __indexOfFilteredItems = [item,]
238 # Dans le cas où la sortie donne les valeurs d'un "outputTag"
239 if outputTag is not None and isinstance(outputTag, str):
241 for index in __indexOfFilteredItems:
242 if outputTag in self.__tags[index].keys():
243 outputValues.append( self.__tags[index][outputTag] )
244 outputValues = sorted(set(outputValues))
247 # Dans le cas où la sortie donne les tags satisfaisants aux conditions
250 return [self.__tags[index] for index in __indexOfFilteredItems]
253 for index in __indexOfFilteredItems:
254 allTags.update( self.__tags[index] )
255 allKeys = sorted(allTags.keys())
258 # ---------------------------------------------------------
260 def stepnumber(self):
262 return len(self.__values)
265 def stepserie(self, **kwargs):
266 "Nombre de pas filtrés"
267 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
268 return __indexOfFilteredItems
271 def steplist(self, **kwargs):
272 "Nombre de pas filtrés"
273 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
274 return list(__indexOfFilteredItems)
276 # ---------------------------------------------------------
279 Renvoie la série contenant, à chaque pas, la valeur moyenne des données
280 au pas. Il faut que le type de base soit compatible avec les types
284 return [numpy.mean(item, dtype=mfp).astype('float') for item in self.__values]
286 raise TypeError("Base type is incompatible with numpy")
288 def stds(self, ddof=0):
290 Renvoie la série contenant, à chaque pas, l'écart-type des données
291 au pas. Il faut que le type de base soit compatible avec les types
294 ddof : c'est le nombre de degrés de liberté pour le calcul de
295 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
298 if numpy.version.version >= '1.1.0':
299 return [numpy.array(item).std(ddof=ddof, dtype=mfp).astype('float') for item in self.__values]
301 return [numpy.array(item).std(dtype=mfp).astype('float') for item in self.__values]
303 raise TypeError("Base type is incompatible with numpy")
307 Renvoie la série contenant, à chaque pas, la somme des données au pas.
308 Il faut que le type de base soit compatible avec les types élémentaires
312 return [numpy.array(item).sum() for item in self.__values]
314 raise TypeError("Base type is incompatible with numpy")
318 Renvoie la série contenant, à chaque pas, le minimum des données au pas.
319 Il faut que le type de base soit compatible avec les types élémentaires
323 return [numpy.array(item).min() for item in self.__values]
325 raise TypeError("Base type is incompatible with numpy")
329 Renvoie la série contenant, à chaque pas, la maximum des données au pas.
330 Il faut que le type de base soit compatible avec les types élémentaires
334 return [numpy.array(item).max() for item in self.__values]
336 raise TypeError("Base type is incompatible with numpy")
338 def powers(self, x2):
340 Renvoie la série contenant, à chaque pas, la puissance "**x2" au pas.
341 Il faut que le type de base soit compatible avec les types élémentaires
345 return [numpy.power(item, x2) for item in self.__values]
347 raise TypeError("Base type is incompatible with numpy")
349 def norms(self, _ord=None):
351 Norm (_ord : voir numpy.linalg.norm)
353 Renvoie la série contenant, à chaque pas, la norme des données au pas.
354 Il faut que le type de base soit compatible avec les types élémentaires
358 return [numpy.linalg.norm(item, _ord) for item in self.__values]
360 raise TypeError("Base type is incompatible with numpy")
362 def traces(self, offset=0):
366 Renvoie la série contenant, à chaque pas, la trace (avec l'offset) des
367 données au pas. Il faut que le type de base soit compatible avec les
368 types élémentaires numpy.
371 return [numpy.trace(item, offset, dtype=mfp) for item in self.__values]
373 raise TypeError("Base type is incompatible with numpy")
375 def maes(self, _predictor=None):
377 Mean Absolute Error (MAE)
378 mae(dX) = 1/n sum(dX_i)
380 Renvoie la série contenant, à chaque pas, la MAE des données au pas.
381 Il faut que le type de base soit compatible avec les types élémentaires
382 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
383 prédicteur est None, sinon c'est appliqué à l'écart entre les données
384 au pas et le prédicteur au même pas.
386 if _predictor is None:
388 return [numpy.mean(numpy.abs(item)) 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 return [numpy.mean(numpy.abs(numpy.ravel(item) - numpy.ravel(_predictor[i]))) for i, item in enumerate(self.__values)]
400 raise TypeError("Base type is incompatible with numpy")
402 def mses(self, _predictor=None):
404 Mean-Square Error (MSE) ou Mean-Square Deviation (MSD)
405 mse(dX) = 1/n sum(dX_i**2)
407 Renvoie la série contenant, à chaque pas, la MSE des données au pas. Il
408 faut que le type de base soit compatible avec les types élémentaires
409 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
410 prédicteur est None, sinon c'est appliqué à l'écart entre les données
411 au pas et le prédicteur au même pas.
413 if _predictor is None:
415 __n = self.shape()[0]
416 return [(numpy.linalg.norm(item)**2 / __n) for item in self.__values]
418 raise TypeError("Base type is incompatible with numpy")
420 if len(_predictor) != len(self.__values):
421 raise ValueError("Predictor number of steps is incompatible with the values")
422 for i, item in enumerate(self.__values):
423 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
424 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
426 __n = self.shape()[0]
427 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i]))**2 / __n) for i, item in enumerate(self.__values)]
429 raise TypeError("Base type is incompatible with numpy")
431 msds = mses # Mean-Square Deviation (MSD=MSE)
433 def rmses(self, _predictor=None):
435 Root-Mean-Square Error (RMSE) ou Root-Mean-Square Deviation (RMSD)
436 rmse(dX) = sqrt( 1/n sum(dX_i**2) ) = sqrt( mse(dX) )
438 Renvoie la série contenant, à chaque pas, la RMSE des données au pas.
439 Il faut que le type de base soit compatible avec les types élémentaires
440 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
441 prédicteur est None, sinon c'est appliqué à l'écart entre les données
442 au pas et le prédicteur au même pas.
444 if _predictor is None:
446 __n = self.shape()[0]
447 return [(numpy.linalg.norm(item) / math.sqrt(__n)) for item in self.__values]
449 raise TypeError("Base type is incompatible with numpy")
451 if len(_predictor) != len(self.__values):
452 raise ValueError("Predictor number of steps is incompatible with the values")
453 for i, item in enumerate(self.__values):
454 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
455 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
457 __n = self.shape()[0]
458 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i])) / math.sqrt(__n)) for i, item in enumerate(self.__values)]
460 raise TypeError("Base type is incompatible with numpy")
462 rmsds = rmses # Root-Mean-Square Deviation (RMSD=RMSE)
469 geometry = "600x400",
472 "Préparation des plots"
474 # Vérification de la disponibilité du module Gnuplot
476 raise ImportError("The Gnuplot module is required to plot the object.")
478 # Vérification et compléments sur les paramètres d'entrée
481 __geometry = str(geometry)
482 __sizespec = (__geometry.split('+')[0]).replace('x', ',')
485 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
487 self.__g = Gnuplot.Gnuplot() # persist=1
488 self.__g('set terminal ' + Gnuplot.GnuplotOpts.default_term + ' size ' + __sizespec)
489 self.__g('set style data lines')
491 self.__g('set autoscale')
492 self.__g('set xlabel "' + str(xlabel) + '"')
493 self.__g('set ylabel "' + str(ylabel) + '"')
495 self.__ltitle = ltitle
506 geometry = "600x400",
512 Renvoie un affichage de la valeur à chaque pas, si elle est compatible
513 avec un affichage Gnuplot (donc essentiellement un vecteur). Si
514 l'argument "step" existe dans la liste des pas de stockage effectués,
515 renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
516 "item" est correct, renvoie l'affichage de la valeur stockée au numéro
517 "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
518 affichage successif de tous les pas.
521 - step : valeur du pas à afficher
522 - item : index de la valeur à afficher
523 - steps : liste unique des pas de l'axe des X, ou None si c'est
524 la numérotation par défaut
525 - title : base du titre général, qui sera automatiquement
526 complétée par la mention du pas
527 - xlabel : label de l'axe des X
528 - ylabel : label de l'axe des Y
529 - ltitle : titre associé au vecteur tracé
530 - geometry : taille en pixels de la fenêtre et position du coin haut
531 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
532 - filename : base de nom de fichier Postscript pour une sauvegarde,
533 qui est automatiquement complétée par le numéro du
534 fichier calculé par incrément simple de compteur
535 - dynamic : effectue un affichage des valeurs à chaque stockage
536 (au-delà du second). La méthode "plots" permet de
537 déclarer l'affichage dynamique, et c'est la méthode
538 "__replots" qui est utilisée pour l'effectuer
539 - persist : booléen indiquant que la fenêtre affichée sera
540 conservée lors du passage au dessin suivant
541 Par défaut, persist = False
542 - pause : booléen indiquant une pause après chaque tracé, et
544 Par défaut, pause = True
546 if not self.__dynamic:
547 self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
549 self.__dynamic = True
550 if len(self.__values) == 0:
553 # Tracé du ou des vecteurs demandés
555 if step is not None and step < len(self.__values):
557 elif item is not None and item < len(self.__values):
560 indexes = indexes + list(range(len(self.__values)))
563 for index in indexes:
564 self.__g('set title "' + str(title) + ' (pas ' + str(index) + ')"')
565 if isinstance(steps, (list, numpy.ndarray)):
568 Steps = list(range(len(self.__values[index])))
570 self.__g.plot( Gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
574 stepfilename = "%s_%03i.ps"%(filename, i)
575 if os.path.isfile(stepfilename):
576 raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
577 self.__g.hardcopy(filename=stepfilename, color=1)
579 eval(input('Please press return to continue...\n'))
583 Affichage dans le cas du suivi dynamique de la variable
585 if self.__dynamic and len(self.__values) < 2:
588 self.__g('set title "' + str(self.__title))
589 Steps = list(range(len(self.__values)))
590 self.__g.plot( Gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
593 eval(input('Please press return to continue...\n'))
595 # ---------------------------------------------------------
596 # On pourrait aussi utiliser d'autres attributs d'un "array" comme "tofile"
599 Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
600 longueur des pas. Il faut que le type de base soit compatible avec
601 les types élémentaires numpy.
604 return numpy.mean(self.__values, axis=0, dtype=mfp).astype('float')
606 raise TypeError("Base type is incompatible with numpy")
608 def std(self, ddof=0):
610 Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
611 longueur des pas. Il faut que le type de base soit compatible avec
612 les types élémentaires numpy.
614 ddof : c'est le nombre de degrés de liberté pour le calcul de
615 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
618 if numpy.version.version >= '1.1.0':
619 return numpy.asarray(self.__values).std(ddof=ddof, axis=0).astype('float')
621 return numpy.asarray(self.__values).std(axis=0).astype('float')
623 raise TypeError("Base type is incompatible with numpy")
627 Renvoie la somme de toutes les valeurs sans tenir compte de la
628 longueur des pas. Il faut que le type de base soit compatible avec
629 les types élémentaires numpy.
632 return numpy.asarray(self.__values).sum(axis=0)
634 raise TypeError("Base type is incompatible with numpy")
638 Renvoie le minimum de toutes les valeurs sans tenir compte de la
639 longueur des pas. Il faut que le type de base soit compatible avec
640 les types élémentaires numpy.
643 return numpy.asarray(self.__values).min(axis=0)
645 raise TypeError("Base type is incompatible with numpy")
649 Renvoie le maximum de toutes les valeurs sans tenir compte de la
650 longueur des pas. Il faut que le type de base soit compatible avec
651 les types élémentaires numpy.
654 return numpy.asarray(self.__values).max(axis=0)
656 raise TypeError("Base type is incompatible with numpy")
660 Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
661 longueur des pas. Il faut que le type de base soit compatible avec
662 les types élémentaires numpy.
665 return numpy.asarray(self.__values).cumsum(axis=0)
667 raise TypeError("Base type is incompatible with numpy")
675 geometry = "600x400",
680 Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
681 elles sont compatibles avec un affichage Gnuplot (donc essentiellement
682 un vecteur). Si l'argument "step" existe dans la liste des pas de
683 stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
684 "step". Si l'argument "item" est correct, renvoie l'affichage de la
685 valeur stockée au numéro "item".
688 - steps : liste unique des pas de l'axe des X, ou None si c'est
689 la numérotation par défaut
690 - title : base du titre général, qui sera automatiquement
691 complétée par la mention du pas
692 - xlabel : label de l'axe des X
693 - ylabel : label de l'axe des Y
694 - ltitle : titre associé au vecteur tracé
695 - geometry : taille en pixels de la fenêtre et position du coin haut
696 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
697 - filename : nom de fichier Postscript pour une sauvegarde
698 - persist : booléen indiquant que la fenêtre affichée sera
699 conservée lors du passage au dessin suivant
700 Par défaut, persist = False
701 - pause : booléen indiquant une pause après chaque tracé, et
703 Par défaut, pause = True
706 # Vérification de la disponibilité du module Gnuplot
708 raise ImportError("The Gnuplot module is required to plot the object.")
710 # Vérification et compléments sur les paramètres d'entrée
713 if isinstance(steps, (list, numpy.ndarray)):
716 Steps = list(range(len(self.__values[0])))
717 __geometry = str(geometry)
718 __sizespec = (__geometry.split('+')[0]).replace('x', ',')
721 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
723 self.__g = Gnuplot.Gnuplot() # persist=1
724 self.__g('set terminal ' + Gnuplot.GnuplotOpts.default_term + ' size ' + __sizespec)
725 self.__g('set style data lines')
727 self.__g('set autoscale')
728 self.__g('set title "' + str(title) + '"')
729 self.__g('set xlabel "' + str(xlabel) + '"')
730 self.__g('set ylabel "' + str(ylabel) + '"')
732 # Tracé du ou des vecteurs demandés
733 indexes = list(range(len(self.__values)))
734 self.__g.plot( Gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle + " (pas 0)" ) )
735 for index in indexes:
736 self.__g.replot( Gnuplot.Data( Steps, self.__values[index], title=ltitle + " (pas %i)"%index ) )
739 self.__g.hardcopy(filename=filename, color=1)
741 eval(input('Please press return to continue...\n'))
743 # ---------------------------------------------------------
746 Renvoie la série sous la forme d'une unique matrice avec les données au
747 pas rangées par ligne
750 return numpy.asarray(self.__values)
752 raise TypeError("Base type is incompatible with numpy")
756 Renvoie la série sous la forme d'une unique matrice avec les données au
757 pas rangées par colonne
760 return numpy.asarray(self.__values).transpose()
761 # Eqvlt: return numpy.stack([numpy.ravel(sv) for sv in self.__values], axis=1)
763 raise TypeError("Base type is incompatible with numpy")
765 # ---------------------------------------------------------
766 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
768 Association à la variable d'un triplet définissant un observer
770 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
774 # Vérification du Scheduler
775 # -------------------------
777 if isinstance(Scheduler, int): # Considéré comme une fréquence à partir de 0
778 Schedulers = range( 0, maxiter, int(Scheduler) )
779 elif isinstance(Scheduler, range): # Considéré comme un itérateur
780 Schedulers = Scheduler
781 elif isinstance(Scheduler, (list, tuple)): # Considéré comme des index explicites
782 Schedulers = [int(i) for i in Scheduler] # Similaire à map( int, Scheduler ) # noqa: E262
783 else: # Dans tous les autres cas, activé par défaut
784 Schedulers = range( 0, maxiter )
786 # Stockage interne de l'observer dans la variable
787 # -----------------------------------------------
788 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
790 def removeDataObserver(self, HookFunction = None, AllObservers = False):
792 Suppression d'un observer nommé sur la variable.
794 On peut donner dans HookFunction la meme fonction que lors de la
795 définition, ou un simple string qui est le nom de la fonction. Si
796 AllObservers est vrai, supprime tous les observers enregistrés.
798 if hasattr(HookFunction, "func_name"):
799 name = str( HookFunction.func_name )
800 elif hasattr(HookFunction, "__name__"):
801 name = str( HookFunction.__name__ )
802 elif isinstance(HookFunction, str):
803 name = str( HookFunction )
809 for [hf, hp, hs] in self.__dataobservers:
811 if name is hf.__name__ or AllObservers:
812 index_to_remove.append( ih )
813 index_to_remove.reverse()
814 for ih in index_to_remove:
815 self.__dataobservers.pop( ih )
816 return len(index_to_remove)
818 def hasDataObserver(self):
819 return bool(len(self.__dataobservers) > 0)
821 # ==============================================================================
822 class SchedulerTrigger(object):
824 Classe générale d'interface de type Scheduler/Trigger
829 simplifiedCombo = None,
831 endTime = int( 1e9 ),
837 # ==============================================================================
838 class OneScalar(Persistence):
840 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
842 Le type de base peut être changé par la méthode "basetype", mais il faut que
843 le nouveau type de base soit compatible avec les types par éléments de
844 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
845 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
846 pour conserver une signification claire des noms.
850 def __init__(self, name="", unit="", basetype = float):
851 Persistence.__init__(self, name, unit, basetype)
853 class OneIndex(Persistence):
855 Classe définissant le stockage d'une valeur unique entière (int) par pas.
859 def __init__(self, name="", unit="", basetype = int):
860 Persistence.__init__(self, name, unit, basetype)
862 class OneVector(Persistence):
864 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
865 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
869 def __init__(self, name="", unit="", basetype = numpy.ravel):
870 Persistence.__init__(self, name, unit, basetype)
872 class OneMatrice(Persistence):
874 Classe de stockage d'une matrice de valeurs homogènes par pas.
878 def __init__(self, name="", unit="", basetype = numpy.array):
879 Persistence.__init__(self, name, unit, basetype)
881 class OneMatrix(Persistence):
883 Classe de stockage d'une matrice de valeurs homogènes par pas.
887 def __init__(self, name="", unit="", basetype = numpy.matrix):
888 Persistence.__init__(self, name, unit, basetype)
890 class OneList(Persistence):
892 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
893 pas utiliser cette classe pour des données numériques homogènes, mais
898 def __init__(self, name="", unit="", basetype = list):
899 Persistence.__init__(self, name, unit, basetype)
902 "Fonction transparente, sans effet sur son argument"
905 class OneNoType(Persistence):
907 Classe de stockage d'un objet sans modification (cast) de type. Attention,
908 selon le véritable type de l'objet stocké à chaque pas, les opérations
909 arithmétiques à base de numpy peuvent être invalides ou donner des
910 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
911 volontairement, et pas du tout par défaut.
915 def __init__(self, name="", unit="", basetype = NoType):
916 Persistence.__init__(self, name, unit, basetype)
918 # ==============================================================================
919 class CompositePersistence(object):
921 Structure de stockage permettant de rassembler plusieurs objets de
924 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
927 __slots__ = ("__name", "__StoredObjects")
929 def __init__(self, name="", defaults=True):
933 La gestion interne des données est exclusivement basée sur les
934 variables initialisées ici (qui ne sont pas accessibles depuis
935 l'extérieur des objets comme des attributs) :
936 __StoredObjects : objets de type persistence collectés dans cet objet
938 self.__name = str(name)
940 self.__StoredObjects = {}
942 # Definition des objets par defaut
943 # --------------------------------
945 self.__StoredObjects["Informations"] = OneNoType("Informations")
946 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
947 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
948 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
949 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
950 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
951 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
952 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
953 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
954 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
955 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
956 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
957 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
960 def store(self, name=None, value=None, **kwargs):
962 Stockage d'une valeur "value" pour le "step" dans la variable "name".
965 raise ValueError("Storable object name is required for storage.")
966 if name not in self.__StoredObjects.keys():
967 raise ValueError("No such name '%s' exists in storable objects."%name)
968 self.__StoredObjects[name].store( value=value, **kwargs )
970 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
972 Ajoute dans les objets stockables un nouvel objet défini par son nom,
973 son type de Persistence et son type de base à chaque pas.
976 raise ValueError("Object name is required for adding an object.")
977 if name in self.__StoredObjects.keys():
978 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
980 self.__StoredObjects[name] = persistenceType( name=str(name) )
982 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
984 def get_object(self, name=None ):
986 Renvoie l'objet de type Persistence qui porte le nom demandé.
989 raise ValueError("Object name is required for retrieving an object.")
990 if name not in self.__StoredObjects.keys():
991 raise ValueError("No such name '%s' exists in stored objects."%name)
992 return self.__StoredObjects[name]
994 def set_object(self, name=None, objet=None ):
996 Affecte directement un 'objet' qui porte le nom 'name' demandé.
997 Attention, il n'est pas effectué de vérification sur le type, qui doit
998 comporter les méthodes habituelles de Persistence pour que cela
1002 raise ValueError("Object name is required for setting an object.")
1003 if name in self.__StoredObjects.keys():
1004 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
1005 self.__StoredObjects[name] = objet
1007 def del_object(self, name=None ):
1009 Supprime un objet de la liste des objets stockables.
1012 raise ValueError("Object name is required for retrieving an object.")
1013 if name not in self.__StoredObjects.keys():
1014 raise ValueError("No such name '%s' exists in stored objects."%name)
1015 del self.__StoredObjects[name]
1017 # ---------------------------------------------------------
1018 # Méthodes d'accès de type dictionnaire
1019 def __getitem__(self, name=None ):
1020 "x.__getitem__(y) <==> x[y]"
1021 return self.get_object( name )
1023 def __setitem__(self, name=None, objet=None ):
1024 "x.__setitem__(i, y) <==> x[i]=y"
1025 self.set_object( name, objet )
1028 "D.keys() -> list of D's keys"
1029 return self.get_stored_objects(hideVoidObjects = False)
1032 "D.values() -> list of D's values"
1033 return self.__StoredObjects.values()
1036 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
1037 return self.__StoredObjects.items()
1039 # ---------------------------------------------------------
1040 def get_stored_objects(self, hideVoidObjects = False):
1041 "Renvoie la liste des objets présents"
1042 objs = self.__StoredObjects.keys()
1047 if len(self.__StoredObjects[k]) > 0:
1048 usedObjs.append( k )
1055 # ---------------------------------------------------------
1056 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
1058 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
1059 et renvoi le nom du fichier
1061 if filename is None:
1062 if compress == "gzip":
1063 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
1064 elif compress == "bzip2":
1065 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
1067 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1069 filename = os.path.abspath( filename )
1071 if mode == "pickle":
1072 if compress == "gzip":
1073 output = gzip.open( filename, 'wb')
1074 elif compress == "bzip2":
1075 output = bz2.BZ2File( filename, 'wb')
1077 output = open( filename, 'wb')
1078 pickle.dump(self, output)
1081 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1085 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1087 Recharge un objet composite sauvé en fichier
1089 if filename is None:
1090 raise ValueError("A file name if requested to load a composite.")
1092 filename = os.path.abspath( filename )
1094 if mode == "pickle":
1095 if compress == "gzip":
1096 pkl_file = gzip.open( filename, 'rb')
1097 elif compress == "bzip2":
1098 pkl_file = bz2.BZ2File( filename, 'rb')
1100 pkl_file = open(filename, 'rb')
1101 output = pickle.load(pkl_file)
1102 for k in output.keys():
1105 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1109 # ==============================================================================
1110 if __name__ == "__main__":
1111 print("\n AUTODIAGNOSTIC\n")