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 PlatformInfo
36 mfp = lpi.MaximumPrecision()
41 # ==============================================================================
42 class Persistence(object):
44 Classe générale de persistance définissant les accesseurs nécessaires
48 "__name", "__unit", "__basetype", "__values", "__tags", "__dynamic",
49 "__g", "__title", "__ltitle", "__pause", "__dataobservers",
52 def __init__(self, name="", unit="", basetype=str):
56 basetype : type de base de l'objet stocké à chaque pas
58 La gestion interne des données est exclusivement basée sur les variables
59 initialisées ici (qui ne sont pas accessibles depuis l'extérieur des
60 objets comme des attributs) :
61 __basetype : le type de base de chaque valeur, sous la forme d'un type
62 permettant l'instanciation ou le casting Python
63 __values : les valeurs de stockage. Par défaut, c'est None
65 self.__name = str(name)
66 self.__unit = str(unit)
68 self.__basetype = basetype
73 self.__dynamic = False
79 self.__dataobservers = []
81 def basetype(self, basetype=None):
83 Renvoie ou met en place le type de base des objets stockés
86 return self.__basetype
88 self.__basetype = basetype
90 def store(self, value=None, **kwargs):
92 Stocke une valeur avec ses informations de filtrage.
95 raise ValueError("Value argument required")
97 self.__values.append(copy.copy(self.__basetype(value)))
98 self.__tags.append(kwargs)
102 __step = len(self.__values) - 1
103 for hook, parameters, scheduler in self.__dataobservers:
104 if __step in scheduler:
105 hook( self, parameters )
107 def pop(self, item=None):
109 Retire une valeur enregistrée par son index de stockage. Sans argument,
110 retire le dernier objet enregistre.
114 self.__values.pop(__index)
115 self.__tags.pop(__index)
122 Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un
123 objet numpy, renvoie le shape. Si c'est un entier, un flottant, un
124 complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la
125 longueur. Par défaut, renvoie 1.
127 if len(self.__values) > 0:
128 if self.__basetype in [numpy.matrix, numpy.ndarray, numpy.array, numpy.ravel]:
129 return self.__values[-1].shape
130 elif self.__basetype in [int, float]:
132 elif self.__basetype in [list, dict]:
133 return (len(self.__values[-1]),)
137 raise ValueError("Object has no shape before its first storage")
139 # ---------------------------------------------------------
141 "x.__str__() <==> str(x)"
142 msg = " Index Value Tags\n"
143 for iv, vv in enumerate(self.__values):
144 msg += " i=%05i %10s %s\n"%(iv, vv, self.__tags[iv])
148 "x.__len__() <==> len(x)"
149 return len(self.__values)
154 def __getitem__(self, index=None ):
155 "x.__getitem__(y) <==> x[y]"
156 return copy.copy(self.__values[index])
158 def count(self, value):
159 "L.count(value) -> integer -- return number of occurrences of value"
160 return self.__values.count(value)
162 def index(self, value, start=0, stop=None):
163 "L.index(value, [start, [stop]]) -> integer -- return first index of value."
165 stop = len(self.__values)
166 return self.__values.index(value, start, stop)
168 # ---------------------------------------------------------
169 def __filteredIndexes(self, **kwargs):
170 "Function interne filtrant les index"
171 __indexOfFilteredItems = range(len(self.__tags))
172 __filteringKwTags = kwargs.keys()
173 if len(__filteringKwTags) > 0:
174 for tagKey in __filteringKwTags:
176 for i in __indexOfFilteredItems:
177 if tagKey in self.__tags[i]:
178 if self.__tags[i][tagKey] == kwargs[tagKey]:
180 elif isinstance(kwargs[tagKey], (list, tuple)) and self.__tags[i][tagKey] in kwargs[tagKey]:
182 __indexOfFilteredItems = __tmp
183 if len(__indexOfFilteredItems) == 0:
185 return __indexOfFilteredItems
187 # ---------------------------------------------------------
188 def values(self, **kwargs):
189 "D.values() -> list of D's values"
190 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
191 return [self.__values[i] for i in __indexOfFilteredItems]
193 def keys(self, keyword=None, **kwargs):
194 "D.keys() -> list of D's keys"
195 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
197 for i in __indexOfFilteredItems:
198 if keyword in self.__tags[i]:
199 __keys.append( self.__tags[i][keyword] )
201 __keys.append( None )
204 def items(self, keyword=None, **kwargs):
205 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
206 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
208 for i in __indexOfFilteredItems:
209 if keyword in self.__tags[i]:
210 __pairs.append( (self.__tags[i][keyword], self.__values[i]) )
212 __pairs.append( (None, self.__values[i]) )
216 "D.tagkeys() -> list of D's tag keys"
218 for dicotags in self.__tags:
219 __allKeys.extend( list(dicotags.keys()) )
220 __allKeys = sorted(set(__allKeys))
223 # def valueserie(self, item=None, allSteps=True, **kwargs):
224 # if item is not None:
225 # return self.__values[item]
227 # __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
228 # if not allSteps and len(__indexOfFilteredItems) > 0:
229 # return self.__values[__indexOfFilteredItems[0]]
231 # return [self.__values[i] for i in __indexOfFilteredItems]
233 def tagserie(self, item=None, withValues=False, outputTag=None, **kwargs):
234 "D.tagserie() -> list of D's tag serie"
236 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
238 __indexOfFilteredItems = [item,]
240 # Dans le cas où la sortie donne les valeurs d'un "outputTag"
241 if outputTag is not None and isinstance(outputTag, str):
243 for index in __indexOfFilteredItems:
244 if outputTag in self.__tags[index].keys():
245 outputValues.append( self.__tags[index][outputTag] )
246 outputValues = sorted(set(outputValues))
249 # Dans le cas où la sortie donne les tags satisfaisants aux conditions
252 return [self.__tags[index] for index in __indexOfFilteredItems]
255 for index in __indexOfFilteredItems:
256 allTags.update( self.__tags[index] )
257 allKeys = sorted(allTags.keys())
260 # ---------------------------------------------------------
262 def stepnumber(self):
264 return len(self.__values)
267 def stepserie(self, **kwargs):
268 "Nombre de pas filtrés"
269 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
270 return __indexOfFilteredItems
273 def steplist(self, **kwargs):
274 "Nombre de pas filtrés"
275 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
276 return list(__indexOfFilteredItems)
278 # ---------------------------------------------------------
281 Renvoie la série contenant, à chaque pas, la valeur moyenne des données
282 au pas. Il faut que le type de base soit compatible avec les types
286 return [numpy.mean(item, dtype=mfp).astype('float') for item in self.__values]
288 raise TypeError("Base type is incompatible with numpy")
290 def stds(self, ddof=0):
292 Renvoie la série contenant, à chaque pas, l'écart-type des données
293 au pas. Il faut que le type de base soit compatible avec les types
296 ddof : c'est le nombre de degrés de liberté pour le calcul de
297 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
300 if numpy.version.version >= '1.1.0':
301 return [numpy.array(item).std(ddof=ddof, dtype=mfp).astype('float') for item in self.__values]
303 return [numpy.array(item).std(dtype=mfp).astype('float') for item in self.__values]
305 raise TypeError("Base type is incompatible with numpy")
309 Renvoie la série contenant, à chaque pas, la somme des données au pas.
310 Il faut que le type de base soit compatible avec les types élémentaires
314 return [numpy.array(item).sum() for item in self.__values]
316 raise TypeError("Base type is incompatible with numpy")
320 Renvoie la série contenant, à chaque pas, le minimum des données au pas.
321 Il faut que le type de base soit compatible avec les types élémentaires
325 return [numpy.array(item).min() for item in self.__values]
327 raise TypeError("Base type is incompatible with numpy")
331 Renvoie la série contenant, à chaque pas, la maximum des données au pas.
332 Il faut que le type de base soit compatible avec les types élémentaires
336 return [numpy.array(item).max() for item in self.__values]
338 raise TypeError("Base type is incompatible with numpy")
340 def powers(self, x2):
342 Renvoie la série contenant, à chaque pas, la puissance "**x2" au pas.
343 Il faut que le type de base soit compatible avec les types élémentaires
347 return [numpy.power(item, x2) for item in self.__values]
349 raise TypeError("Base type is incompatible with numpy")
351 def norms(self, _ord=None):
353 Norm (_ord : voir numpy.linalg.norm)
355 Renvoie la série contenant, à chaque pas, la norme des données au pas.
356 Il faut que le type de base soit compatible avec les types élémentaires
360 return [numpy.linalg.norm(item, _ord) for item in self.__values]
362 raise TypeError("Base type is incompatible with numpy")
364 def traces(self, offset=0):
368 Renvoie la série contenant, à chaque pas, la trace (avec l'offset) des
369 données au pas. Il faut que le type de base soit compatible avec les
370 types élémentaires numpy.
373 return [numpy.trace(item, offset, dtype=mfp) for item in self.__values]
375 raise TypeError("Base type is incompatible with numpy")
377 def maes(self, _predictor=None):
379 Mean Absolute Error (MAE)
380 mae(dX) = 1/n sum(dX_i)
382 Renvoie la série contenant, à chaque pas, la MAE des données au pas.
383 Il faut que le type de base soit compatible avec les types élémentaires
384 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
385 prédicteur est None, sinon c'est appliqué à l'écart entre les données
386 au pas et le prédicteur au même pas.
388 if _predictor is None:
390 return [numpy.mean(numpy.abs(item)) for item in self.__values]
392 raise TypeError("Base type is incompatible with numpy")
394 if len(_predictor) != len(self.__values):
395 raise ValueError("Predictor number of steps is incompatible with the values")
396 for i, item in enumerate(self.__values):
397 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
398 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
400 return [numpy.mean(numpy.abs(numpy.ravel(item) - numpy.ravel(_predictor[i]))) for i, item in enumerate(self.__values)]
402 raise TypeError("Base type is incompatible with numpy")
404 def mses(self, _predictor=None):
406 Mean-Square Error (MSE) ou Mean-Square Deviation (MSD)
407 mse(dX) = 1/n sum(dX_i**2)
409 Renvoie la série contenant, à chaque pas, la MSE des données au pas. Il
410 faut que le type de base soit compatible avec les types élémentaires
411 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
412 prédicteur est None, sinon c'est appliqué à l'écart entre les données
413 au pas et le prédicteur au même pas.
415 if _predictor is None:
417 __n = self.shape()[0]
418 return [(numpy.linalg.norm(item)**2 / __n) for item in self.__values]
420 raise TypeError("Base type is incompatible with numpy")
422 if len(_predictor) != len(self.__values):
423 raise ValueError("Predictor number of steps is incompatible with the values")
424 for i, item in enumerate(self.__values):
425 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
426 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
428 __n = self.shape()[0]
429 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i]))**2 / __n) for i, item in enumerate(self.__values)]
431 raise TypeError("Base type is incompatible with numpy")
433 msds = mses # Mean-Square Deviation (MSD=MSE)
435 def rmses(self, _predictor=None):
437 Root-Mean-Square Error (RMSE) ou Root-Mean-Square Deviation (RMSD)
438 rmse(dX) = sqrt( 1/n sum(dX_i**2) ) = sqrt( mse(dX) )
440 Renvoie la série contenant, à chaque pas, la RMSE des données au pas.
441 Il faut que le type de base soit compatible avec les types élémentaires
442 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
443 prédicteur est None, sinon c'est appliqué à l'écart entre les données
444 au pas et le prédicteur au même pas.
446 if _predictor is None:
448 __n = self.shape()[0]
449 return [(numpy.linalg.norm(item) / math.sqrt(__n)) for item in self.__values]
451 raise TypeError("Base type is incompatible with numpy")
453 if len(_predictor) != len(self.__values):
454 raise ValueError("Predictor number of steps is incompatible with the values")
455 for i, item in enumerate(self.__values):
456 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
457 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
459 __n = self.shape()[0]
460 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i])) / math.sqrt(__n)) for i, item in enumerate(self.__values)]
462 raise TypeError("Base type is incompatible with numpy")
464 rmsds = rmses # Root-Mean-Square Deviation (RMSD=RMSE)
471 geometry = "600x400",
474 "Préparation des plots"
476 # Vérification de la disponibilité du module Gnuplot
477 if not lpi.has_gnuplot:
478 raise ImportError("The Gnuplot module is required to plot the object.")
480 # Vérification et compléments sur les paramètres d'entrée
483 __geometry = str(geometry)
484 __sizespec = (__geometry.split('+')[0]).replace('x', ',')
487 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
489 self.__g = Gnuplot.Gnuplot() # persist=1
490 self.__g('set terminal ' + Gnuplot.GnuplotOpts.default_term + ' size ' + __sizespec)
491 self.__g('set style data lines')
493 self.__g('set autoscale')
494 self.__g('set xlabel "' + str(xlabel) + '"')
495 self.__g('set ylabel "' + str(ylabel) + '"')
497 self.__ltitle = ltitle
508 geometry = "600x400",
514 Renvoie un affichage de la valeur à chaque pas, si elle est compatible
515 avec un affichage Gnuplot (donc essentiellement un vecteur). Si
516 l'argument "step" existe dans la liste des pas de stockage effectués,
517 renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
518 "item" est correct, renvoie l'affichage de la valeur stockée au numéro
519 "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
520 affichage successif de tous les pas.
523 - step : valeur du pas à afficher
524 - item : index de la valeur à afficher
525 - steps : liste unique des pas de l'axe des X, ou None si c'est
526 la numérotation par défaut
527 - title : base du titre général, qui sera automatiquement
528 complétée par la mention du pas
529 - xlabel : label de l'axe des X
530 - ylabel : label de l'axe des Y
531 - ltitle : titre associé au vecteur tracé
532 - geometry : taille en pixels de la fenêtre et position du coin haut
533 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
534 - filename : base de nom de fichier Postscript pour une sauvegarde,
535 qui est automatiquement complétée par le numéro du
536 fichier calculé par incrément simple de compteur
537 - dynamic : effectue un affichage des valeurs à chaque stockage
538 (au-delà du second). La méthode "plots" permet de
539 déclarer l'affichage dynamique, et c'est la méthode
540 "__replots" qui est utilisée pour l'effectuer
541 - persist : booléen indiquant que la fenêtre affichée sera
542 conservée lors du passage au dessin suivant
543 Par défaut, persist = False
544 - pause : booléen indiquant une pause après chaque tracé, et
546 Par défaut, pause = True
548 if not self.__dynamic:
549 self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
551 self.__dynamic = True
552 if len(self.__values) == 0:
555 # Tracé du ou des vecteurs demandés
557 if step is not None and step < len(self.__values):
559 elif item is not None and item < len(self.__values):
562 indexes = indexes + list(range(len(self.__values)))
565 for index in indexes:
566 self.__g('set title "' + str(title) + ' (pas ' + str(index) + ')"')
567 if isinstance(steps, (list, numpy.ndarray)):
570 Steps = list(range(len(self.__values[index])))
572 self.__g.plot( Gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
576 stepfilename = "%s_%03i.ps"%(filename, i)
577 if os.path.isfile(stepfilename):
578 raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
579 self.__g.hardcopy(filename=stepfilename, color=1)
581 eval(input('Please press return to continue...\n'))
585 Affichage dans le cas du suivi dynamique de la variable
587 if self.__dynamic and len(self.__values) < 2:
590 self.__g('set title "' + str(self.__title))
591 Steps = list(range(len(self.__values)))
592 self.__g.plot( Gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
595 eval(input('Please press return to continue...\n'))
597 # ---------------------------------------------------------
598 # On pourrait aussi utiliser d'autres attributs d'un "array" comme "tofile"
601 Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
602 longueur des pas. Il faut que le type de base soit compatible avec
603 les types élémentaires numpy.
606 return numpy.mean(self.__values, axis=0, dtype=mfp).astype('float')
608 raise TypeError("Base type is incompatible with numpy")
610 def std(self, ddof=0):
612 Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
613 longueur des pas. Il faut que le type de base soit compatible avec
614 les types élémentaires numpy.
616 ddof : c'est le nombre de degrés de liberté pour le calcul de
617 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
620 if numpy.version.version >= '1.1.0':
621 return numpy.asarray(self.__values).std(ddof=ddof, axis=0).astype('float')
623 return numpy.asarray(self.__values).std(axis=0).astype('float')
625 raise TypeError("Base type is incompatible with numpy")
629 Renvoie la somme de toutes les valeurs sans tenir compte de la
630 longueur des pas. Il faut que le type de base soit compatible avec
631 les types élémentaires numpy.
634 return numpy.asarray(self.__values).sum(axis=0)
636 raise TypeError("Base type is incompatible with numpy")
640 Renvoie le minimum de toutes les valeurs sans tenir compte de la
641 longueur des pas. Il faut que le type de base soit compatible avec
642 les types élémentaires numpy.
645 return numpy.asarray(self.__values).min(axis=0)
647 raise TypeError("Base type is incompatible with numpy")
651 Renvoie le maximum de toutes les valeurs sans tenir compte de la
652 longueur des pas. Il faut que le type de base soit compatible avec
653 les types élémentaires numpy.
656 return numpy.asarray(self.__values).max(axis=0)
658 raise TypeError("Base type is incompatible with numpy")
662 Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
663 longueur des pas. Il faut que le type de base soit compatible avec
664 les types élémentaires numpy.
667 return numpy.asarray(self.__values).cumsum(axis=0)
669 raise TypeError("Base type is incompatible with numpy")
677 geometry = "600x400",
682 Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
683 elles sont compatibles avec un affichage Gnuplot (donc essentiellement
684 un vecteur). Si l'argument "step" existe dans la liste des pas de
685 stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
686 "step". Si l'argument "item" est correct, renvoie l'affichage de la
687 valeur stockée au numéro "item".
690 - steps : liste unique des pas de l'axe des X, ou None si c'est
691 la numérotation par défaut
692 - title : base du titre général, qui sera automatiquement
693 complétée par la mention du pas
694 - xlabel : label de l'axe des X
695 - ylabel : label de l'axe des Y
696 - ltitle : titre associé au vecteur tracé
697 - geometry : taille en pixels de la fenêtre et position du coin haut
698 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
699 - filename : nom de fichier Postscript pour une sauvegarde
700 - persist : booléen indiquant que la fenêtre affichée sera
701 conservée lors du passage au dessin suivant
702 Par défaut, persist = False
703 - pause : booléen indiquant une pause après chaque tracé, et
705 Par défaut, pause = True
708 # Vérification de la disponibilité du module Gnuplot
709 if not lpi.has_gnuplot:
710 raise ImportError("The Gnuplot module is required to plot the object.")
712 # Vérification et compléments sur les paramètres d'entrée
715 if isinstance(steps, (list, numpy.ndarray)):
718 Steps = list(range(len(self.__values[0])))
719 __geometry = str(geometry)
720 __sizespec = (__geometry.split('+')[0]).replace('x', ',')
723 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
725 self.__g = Gnuplot.Gnuplot() # persist=1
726 self.__g('set terminal ' + Gnuplot.GnuplotOpts.default_term + ' size ' + __sizespec)
727 self.__g('set style data lines')
729 self.__g('set autoscale')
730 self.__g('set title "' + str(title) + '"')
731 self.__g('set xlabel "' + str(xlabel) + '"')
732 self.__g('set ylabel "' + str(ylabel) + '"')
734 # Tracé du ou des vecteurs demandés
735 indexes = list(range(len(self.__values)))
736 self.__g.plot( Gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle + " (pas 0)" ) )
737 for index in indexes:
738 self.__g.replot( Gnuplot.Data( Steps, self.__values[index], title=ltitle + " (pas %i)"%index ) )
741 self.__g.hardcopy(filename=filename, color=1)
743 eval(input('Please press return to continue...\n'))
745 # ---------------------------------------------------------
748 Renvoie la série sous la forme d'une unique matrice avec les données au
749 pas rangées par ligne
752 return numpy.asarray(self.__values)
754 raise TypeError("Base type is incompatible with numpy")
758 Renvoie la série sous la forme d'une unique matrice avec les données au
759 pas rangées par colonne
762 return numpy.asarray(self.__values).transpose()
763 # Eqvlt: return numpy.stack([numpy.ravel(sv) for sv in self.__values], axis=1)
765 raise TypeError("Base type is incompatible with numpy")
767 # ---------------------------------------------------------
768 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
770 Association à la variable d'un triplet définissant un observer
772 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
776 # Vérification du Scheduler
777 # -------------------------
779 if isinstance(Scheduler, int): # Considéré comme une fréquence à partir de 0
780 Schedulers = range( 0, maxiter, int(Scheduler) )
781 elif isinstance(Scheduler, range): # Considéré comme un itérateur
782 Schedulers = Scheduler
783 elif isinstance(Scheduler, (list, tuple)): # Considéré comme des index explicites
784 Schedulers = [int(i) for i in Scheduler] # Similaire à map( int, Scheduler ) # noqa: E262
785 else: # Dans tous les autres cas, activé par défaut
786 Schedulers = range( 0, maxiter )
788 # Stockage interne de l'observer dans la variable
789 # -----------------------------------------------
790 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
792 def removeDataObserver(self, HookFunction = None, AllObservers = False):
794 Suppression d'un observer nommé sur la variable.
796 On peut donner dans HookFunction la meme fonction que lors de la
797 définition, ou un simple string qui est le nom de la fonction. Si
798 AllObservers est vrai, supprime tous les observers enregistrés.
800 if hasattr(HookFunction, "func_name"):
801 name = str( HookFunction.func_name )
802 elif hasattr(HookFunction, "__name__"):
803 name = str( HookFunction.__name__ )
804 elif isinstance(HookFunction, str):
805 name = str( HookFunction )
811 for [hf, hp, hs] in self.__dataobservers:
813 if name is hf.__name__ or AllObservers:
814 index_to_remove.append( ih )
815 index_to_remove.reverse()
816 for ih in index_to_remove:
817 self.__dataobservers.pop( ih )
818 return len(index_to_remove)
820 def hasDataObserver(self):
821 return bool(len(self.__dataobservers) > 0)
823 # ==============================================================================
824 class SchedulerTrigger(object):
826 Classe générale d'interface de type Scheduler/Trigger
831 simplifiedCombo = None,
833 endTime = int( 1e9 ),
839 # ==============================================================================
840 class OneScalar(Persistence):
842 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
844 Le type de base peut être changé par la méthode "basetype", mais il faut que
845 le nouveau type de base soit compatible avec les types par éléments de
846 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
847 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
848 pour conserver une signification claire des noms.
852 def __init__(self, name="", unit="", basetype = float):
853 Persistence.__init__(self, name, unit, basetype)
855 class OneIndex(Persistence):
857 Classe définissant le stockage d'une valeur unique entière (int) par pas.
861 def __init__(self, name="", unit="", basetype = int):
862 Persistence.__init__(self, name, unit, basetype)
864 class OneVector(Persistence):
866 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
867 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
871 def __init__(self, name="", unit="", basetype = numpy.ravel):
872 Persistence.__init__(self, name, unit, basetype)
874 class OneMatrice(Persistence):
876 Classe de stockage d'une matrice de valeurs homogènes par pas.
880 def __init__(self, name="", unit="", basetype = numpy.array):
881 Persistence.__init__(self, name, unit, basetype)
883 class OneMatrix(Persistence):
885 Classe de stockage d'une matrice de valeurs homogènes par pas.
889 def __init__(self, name="", unit="", basetype = numpy.matrix):
890 Persistence.__init__(self, name, unit, basetype)
892 class OneList(Persistence):
894 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
895 pas utiliser cette classe pour des données numériques homogènes, mais
900 def __init__(self, name="", unit="", basetype = list):
901 Persistence.__init__(self, name, unit, basetype)
904 "Fonction transparente, sans effet sur son argument"
907 class OneNoType(Persistence):
909 Classe de stockage d'un objet sans modification (cast) de type. Attention,
910 selon le véritable type de l'objet stocké à chaque pas, les opérations
911 arithmétiques à base de numpy peuvent être invalides ou donner des
912 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
913 volontairement, et pas du tout par défaut.
917 def __init__(self, name="", unit="", basetype = NoType):
918 Persistence.__init__(self, name, unit, basetype)
920 # ==============================================================================
921 class CompositePersistence(object):
923 Structure de stockage permettant de rassembler plusieurs objets de
926 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
929 __slots__ = ("__name", "__StoredObjects")
931 def __init__(self, name="", defaults=True):
935 La gestion interne des données est exclusivement basée sur les
936 variables initialisées ici (qui ne sont pas accessibles depuis
937 l'extérieur des objets comme des attributs) :
938 __StoredObjects : objets de type persistence collectés dans cet objet
940 self.__name = str(name)
942 self.__StoredObjects = {}
944 # Definition des objets par defaut
945 # --------------------------------
947 self.__StoredObjects["Informations"] = OneNoType("Informations")
948 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
949 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
950 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
951 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
952 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
953 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
954 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
955 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
956 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
957 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
958 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
959 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
962 def store(self, name=None, value=None, **kwargs):
964 Stockage d'une valeur "value" pour le "step" dans la variable "name".
967 raise ValueError("Storable object name is required for storage.")
968 if name not in self.__StoredObjects.keys():
969 raise ValueError("No such name '%s' exists in storable objects."%name)
970 self.__StoredObjects[name].store( value=value, **kwargs )
972 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
974 Ajoute dans les objets stockables un nouvel objet défini par son nom,
975 son type de Persistence et son type de base à chaque pas.
978 raise ValueError("Object name is required for adding 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)
982 self.__StoredObjects[name] = persistenceType( name=str(name) )
984 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
986 def get_object(self, name=None ):
988 Renvoie l'objet de type Persistence qui porte le nom demandé.
991 raise ValueError("Object name is required for retrieving an object.")
992 if name not in self.__StoredObjects.keys():
993 raise ValueError("No such name '%s' exists in stored objects."%name)
994 return self.__StoredObjects[name]
996 def set_object(self, name=None, objet=None ):
998 Affecte directement un 'objet' qui porte le nom 'name' demandé.
999 Attention, il n'est pas effectué de vérification sur le type, qui doit
1000 comporter les méthodes habituelles de Persistence pour que cela
1004 raise ValueError("Object name is required for setting an object.")
1005 if name in self.__StoredObjects.keys():
1006 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
1007 self.__StoredObjects[name] = objet
1009 def del_object(self, name=None ):
1011 Supprime un objet de la liste des objets stockables.
1014 raise ValueError("Object name is required for retrieving an object.")
1015 if name not in self.__StoredObjects.keys():
1016 raise ValueError("No such name '%s' exists in stored objects."%name)
1017 del self.__StoredObjects[name]
1019 # ---------------------------------------------------------
1020 # Méthodes d'accès de type dictionnaire
1021 def __getitem__(self, name=None ):
1022 "x.__getitem__(y) <==> x[y]"
1023 return self.get_object( name )
1025 def __setitem__(self, name=None, objet=None ):
1026 "x.__setitem__(i, y) <==> x[i]=y"
1027 self.set_object( name, objet )
1030 "D.keys() -> list of D's keys"
1031 return self.get_stored_objects(hideVoidObjects = False)
1034 "D.values() -> list of D's values"
1035 return self.__StoredObjects.values()
1038 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
1039 return self.__StoredObjects.items()
1041 # ---------------------------------------------------------
1042 def get_stored_objects(self, hideVoidObjects = False):
1043 "Renvoie la liste des objets présents"
1044 objs = self.__StoredObjects.keys()
1049 if len(self.__StoredObjects[k]) > 0:
1050 usedObjs.append( k )
1057 # ---------------------------------------------------------
1058 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
1060 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
1061 et renvoi le nom du fichier
1063 if filename is None:
1064 if compress == "gzip":
1065 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
1066 elif compress == "bzip2":
1067 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
1069 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1071 filename = os.path.abspath( filename )
1073 if mode == "pickle":
1074 if compress == "gzip":
1075 output = gzip.open( filename, 'wb')
1076 elif compress == "bzip2":
1077 output = bz2.BZ2File( filename, 'wb')
1079 output = open( filename, 'wb')
1080 pickle.dump(self, output)
1083 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1087 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1089 Recharge un objet composite sauvé en fichier
1091 if filename is None:
1092 raise ValueError("A file name if requested to load a composite.")
1094 filename = os.path.abspath( filename )
1096 if mode == "pickle":
1097 if compress == "gzip":
1098 pkl_file = gzip.open( filename, 'rb')
1099 elif compress == "bzip2":
1100 pkl_file = bz2.BZ2File( filename, 'rb')
1102 pkl_file = open(filename, 'rb')
1103 output = pickle.load(pkl_file)
1104 for k in output.keys():
1107 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1111 # ==============================================================================
1112 if __name__ == "__main__":
1113 print("\n AUTODIAGNOSTIC\n")