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()
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 traces(self, offset=0):
362 Renvoie la série contenant, à chaque pas, la trace (avec l'offset) des
363 données au pas. Il faut que le type de base soit compatible avec les
364 types élémentaires numpy.
367 return [numpy.trace(item, offset, dtype=mfp) for item in self.__values]
369 raise TypeError("Base type is incompatible with numpy")
371 def maes(self, _predictor=None):
373 Mean Absolute Error (MAE)
374 mae(dX) = 1/n sum(dX_i)
376 Renvoie la série contenant, à chaque pas, la MAE des données au pas.
377 Il faut que le type de base soit compatible avec les types élémentaires
378 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
379 prédicteur est None, sinon c'est appliqué à l'écart entre les données
380 au pas et le prédicteur au même pas.
382 if _predictor is None:
384 return [numpy.mean(numpy.abs(item)) for item in self.__values]
386 raise TypeError("Base type is incompatible with numpy")
388 if len(_predictor) != len(self.__values):
389 raise ValueError("Predictor number of steps is incompatible with the values")
390 for i, item in enumerate(self.__values):
391 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
392 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
394 return [numpy.mean(numpy.abs(numpy.ravel(item) - numpy.ravel(_predictor[i]))) for i, item in enumerate(self.__values)]
396 raise TypeError("Base type is incompatible with numpy")
398 def mses(self, _predictor=None):
400 Mean-Square Error (MSE) ou Mean-Square Deviation (MSD)
401 mse(dX) = 1/n sum(dX_i**2)
403 Renvoie la série contenant, à chaque pas, la MSE des données au pas. Il
404 faut que le type de base soit compatible avec les types élémentaires
405 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
406 prédicteur est None, sinon c'est appliqué à l'écart entre les données
407 au pas et le prédicteur au même pas.
409 if _predictor is None:
411 __n = self.shape()[0]
412 return [(numpy.linalg.norm(item)**2 / __n) for item in self.__values]
414 raise TypeError("Base type is incompatible with numpy")
416 if len(_predictor) != len(self.__values):
417 raise ValueError("Predictor number of steps is incompatible with the values")
418 for i, item in enumerate(self.__values):
419 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
420 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
422 __n = self.shape()[0]
423 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i]))**2 / __n) for i, item in enumerate(self.__values)]
425 raise TypeError("Base type is incompatible with numpy")
427 msds=mses # Mean-Square Deviation (MSD=MSE)
429 def rmses(self, _predictor=None):
431 Root-Mean-Square Error (RMSE) ou Root-Mean-Square Deviation (RMSD)
432 rmse(dX) = sqrt( 1/n sum(dX_i**2) ) = sqrt( mse(dX) )
434 Renvoie la série contenant, à chaque pas, la RMSE des données au pas.
435 Il faut que le type de base soit compatible avec les types élémentaires
436 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
437 prédicteur est None, sinon c'est appliqué à l'écart entre les données
438 au pas et le prédicteur au même pas.
440 if _predictor is None:
442 __n = self.shape()[0]
443 return [(numpy.linalg.norm(item) / math.sqrt(__n)) for item in self.__values]
445 raise TypeError("Base type is incompatible with numpy")
447 if len(_predictor) != len(self.__values):
448 raise ValueError("Predictor number of steps is incompatible with the values")
449 for i, item in enumerate(self.__values):
450 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
451 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
453 __n = self.shape()[0]
454 return [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i])) / math.sqrt(__n)) for i, item in enumerate(self.__values)]
456 raise TypeError("Base type is incompatible with numpy")
458 rmsds = rmses # Root-Mean-Square Deviation (RMSD=RMSE)
465 geometry = "600x400",
469 "Préparation des plots"
471 # Vérification de la disponibilité du module Gnuplot
473 raise ImportError("The Gnuplot module is required to plot the object.")
475 # Vérification et compléments sur les paramètres d'entrée
476 if ltitle is None: ltitle = ""
477 __geometry = str(geometry)
478 __sizespec = (__geometry.split('+')[0]).replace('x',',')
481 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
483 self.__g = Gnuplot.Gnuplot() # persist=1
484 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term+' size '+__sizespec)
485 self.__g('set style data lines')
487 self.__g('set autoscale')
488 self.__g('set xlabel "'+str(xlabel)+'"')
489 self.__g('set ylabel "'+str(ylabel)+'"')
491 self.__ltitle = ltitle
502 geometry = "600x400",
509 Renvoie un affichage de la valeur à chaque pas, si elle est compatible
510 avec un affichage Gnuplot (donc essentiellement un vecteur). Si
511 l'argument "step" existe dans la liste des pas de stockage effectués,
512 renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
513 "item" est correct, renvoie l'affichage de la valeur stockée au numéro
514 "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
515 affichage successif de tous les pas.
518 - step : valeur du pas à afficher
519 - item : index de la valeur à afficher
520 - steps : liste unique des pas de l'axe des X, ou None si c'est
521 la numérotation par défaut
522 - title : base du titre général, qui sera automatiquement
523 complétée par la mention du pas
524 - xlabel : label de l'axe des X
525 - ylabel : label de l'axe des Y
526 - ltitle : titre associé au vecteur tracé
527 - geometry : taille en pixels de la fenêtre et position du coin haut
528 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
529 - filename : base de nom de fichier Postscript pour une sauvegarde,
530 qui est automatiquement complétée par le numéro du
531 fichier calculé par incrément simple de compteur
532 - dynamic : effectue un affichage des valeurs à chaque stockage
533 (au-delà du second). La méthode "plots" permet de
534 déclarer l'affichage dynamique, et c'est la méthode
535 "__replots" qui est utilisée pour l'effectuer
536 - persist : booléen indiquant que la fenêtre affichée sera
537 conservée lors du passage au dessin suivant
538 Par défaut, persist = False
539 - pause : booléen indiquant une pause après chaque tracé, et
541 Par défaut, pause = True
543 if not self.__dynamic:
544 self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
546 self.__dynamic = True
547 if len(self.__values) == 0: return 0
549 # Tracé du ou des vecteurs demandés
551 if step is not None and step < len(self.__values):
553 elif item is not None and item < len(self.__values):
556 indexes = indexes + list(range(len(self.__values)))
559 for index in indexes:
560 self.__g('set title "'+str(title)+' (pas '+str(index)+')"')
561 if isinstance(steps, (list, numpy.ndarray)):
564 Steps = list(range(len(self.__values[index])))
566 self.__g.plot( Gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
570 stepfilename = "%s_%03i.ps"%(filename,i)
571 if os.path.isfile(stepfilename):
572 raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
573 self.__g.hardcopy(filename=stepfilename, color=1)
575 eval(input('Please press return to continue...\n'))
579 Affichage dans le cas du suivi dynamique de la variable
581 if self.__dynamic and len(self.__values) < 2: return 0
583 self.__g('set title "'+str(self.__title))
584 Steps = list(range(len(self.__values)))
585 self.__g.plot( Gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
588 eval(input('Please press return to continue...\n'))
590 # ---------------------------------------------------------
591 # On pourrait aussi utiliser d'autres attributs d'un "array" comme "tofile"
594 Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
595 longueur des pas. Il faut que le type de base soit compatible avec
596 les types élémentaires numpy.
599 return numpy.mean(self.__values, axis=0, dtype=mfp).astype('float')
601 raise TypeError("Base type is incompatible with numpy")
603 def std(self, ddof=0):
605 Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
606 longueur des pas. Il faut que le type de base soit compatible avec
607 les types élémentaires numpy.
609 ddof : c'est le nombre de degrés de liberté pour le calcul de
610 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
613 if numpy.version.version >= '1.1.0':
614 return numpy.asarray(self.__values).std(ddof=ddof,axis=0).astype('float')
616 return numpy.asarray(self.__values).std(axis=0).astype('float')
618 raise TypeError("Base type is incompatible with numpy")
622 Renvoie la somme de toutes les valeurs sans tenir compte de la
623 longueur des pas. Il faut que le type de base soit compatible avec
624 les types élémentaires numpy.
627 return numpy.asarray(self.__values).sum(axis=0)
629 raise TypeError("Base type is incompatible with numpy")
633 Renvoie le minimum de toutes les valeurs sans tenir compte de la
634 longueur des pas. Il faut que le type de base soit compatible avec
635 les types élémentaires numpy.
638 return numpy.asarray(self.__values).min(axis=0)
640 raise TypeError("Base type is incompatible with numpy")
644 Renvoie le maximum de toutes les valeurs sans tenir compte de la
645 longueur des pas. Il faut que le type de base soit compatible avec
646 les types élémentaires numpy.
649 return numpy.asarray(self.__values).max(axis=0)
651 raise TypeError("Base type is incompatible with numpy")
655 Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
656 longueur des pas. Il faut que le type de base soit compatible avec
657 les types élémentaires numpy.
660 return numpy.asarray(self.__values).cumsum(axis=0)
662 raise TypeError("Base type is incompatible with numpy")
670 geometry = "600x400",
676 Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
677 elles sont compatibles avec un affichage Gnuplot (donc essentiellement
678 un vecteur). Si l'argument "step" existe dans la liste des pas de
679 stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
680 "step". Si l'argument "item" est correct, renvoie l'affichage de la
681 valeur stockée au numéro "item".
684 - steps : liste unique des pas de l'axe des X, ou None si c'est
685 la numérotation par défaut
686 - title : base du titre général, qui sera automatiquement
687 complétée par la mention du pas
688 - xlabel : label de l'axe des X
689 - ylabel : label de l'axe des Y
690 - ltitle : titre associé au vecteur tracé
691 - geometry : taille en pixels de la fenêtre et position du coin haut
692 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
693 - filename : nom de fichier Postscript pour une sauvegarde
694 - persist : booléen indiquant que la fenêtre affichée sera
695 conservée lors du passage au dessin suivant
696 Par défaut, persist = False
697 - pause : booléen indiquant une pause après chaque tracé, et
699 Par défaut, pause = True
702 # Vérification de la disponibilité du module Gnuplot
704 raise ImportError("The Gnuplot module is required to plot the object.")
706 # Vérification et compléments sur les paramètres d'entrée
707 if ltitle is None: ltitle = ""
708 if isinstance(steps, (list, numpy.ndarray)):
711 Steps = list(range(len(self.__values[0])))
712 __geometry = str(geometry)
713 __sizespec = (__geometry.split('+')[0]).replace('x',',')
716 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
718 self.__g = Gnuplot.Gnuplot() # persist=1
719 self.__g('set terminal '+Gnuplot.GnuplotOpts.default_term+' size '+__sizespec)
720 self.__g('set style data lines')
722 self.__g('set autoscale')
723 self.__g('set title "'+str(title) +'"')
724 self.__g('set xlabel "'+str(xlabel)+'"')
725 self.__g('set ylabel "'+str(ylabel)+'"')
727 # Tracé du ou des vecteurs demandés
728 indexes = list(range(len(self.__values)))
729 self.__g.plot( Gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle+" (pas 0)" ) )
730 for index in indexes:
731 self.__g.replot( Gnuplot.Data( Steps, self.__values[index], title=ltitle+" (pas %i)"%index ) )
734 self.__g.hardcopy(filename=filename, color=1)
736 eval(input('Please press return to continue...\n'))
738 # ---------------------------------------------------------
741 Renvoie la série sous la forme d'une unique matrice avec les données au
742 pas rangées par ligne
745 return numpy.asarray(self.__values)
747 raise TypeError("Base type is incompatible with numpy")
751 Renvoie la série sous la forme d'une unique matrice avec les données au
752 pas rangées par colonne
755 return numpy.asarray(self.__values).transpose()
756 # Eqvlt: return numpy.stack([numpy.ravel(sv) for sv in self.__values], axis=1)
758 raise TypeError("Base type is incompatible with numpy")
760 # ---------------------------------------------------------
761 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None):
763 Association à la variable d'un triplet définissant un observer
765 Le Scheduler attendu est une fréquence, une simple liste d'index ou un
769 # Vérification du Scheduler
770 # -------------------------
772 if isinstance(Scheduler,int): # Considéré comme une fréquence à partir de 0
773 Schedulers = range( 0, maxiter, int(Scheduler) )
774 elif isinstance(Scheduler,range): # Considéré comme un itérateur
775 Schedulers = Scheduler
776 elif isinstance(Scheduler,(list,tuple)): # Considéré comme des index explicites
777 Schedulers = [int(i) for i in Scheduler] # map( long, Scheduler )
778 else: # Dans tous les autres cas, activé par défaut
779 Schedulers = range( 0, maxiter )
781 # Stockage interne de l'observer dans la variable
782 # -----------------------------------------------
783 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers] )
785 def removeDataObserver(self, HookFunction = None, AllObservers = False):
787 Suppression d'un observer nommé sur la variable.
789 On peut donner dans HookFunction la meme fonction que lors de la
790 définition, ou un simple string qui est le nom de la fonction. Si
791 AllObservers est vrai, supprime tous les observers enregistrés.
793 if hasattr(HookFunction,"func_name"):
794 name = str( HookFunction.func_name )
795 elif hasattr(HookFunction,"__name__"):
796 name = str( HookFunction.__name__ )
797 elif isinstance(HookFunction,str):
798 name = str( HookFunction )
804 for [hf, hp, hs] in self.__dataobservers:
806 if name is hf.__name__ or AllObservers: index_to_remove.append( i )
807 index_to_remove.reverse()
808 for i in index_to_remove:
809 self.__dataobservers.pop( i )
810 return len(index_to_remove)
812 def hasDataObserver(self):
813 return bool(len(self.__dataobservers) > 0)
815 # ==============================================================================
816 class SchedulerTrigger(object):
818 Classe générale d'interface de type Scheduler/Trigger
823 simplifiedCombo = None,
825 endTime = int( 1e9 ),
832 # ==============================================================================
833 class OneScalar(Persistence):
835 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
837 Le type de base peut être changé par la méthode "basetype", mais il faut que
838 le nouveau type de base soit compatible avec les types par éléments de
839 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
840 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
841 pour conserver une signification claire des noms.
845 def __init__(self, name="", unit="", basetype = float):
846 Persistence.__init__(self, name, unit, basetype)
848 class OneIndex(Persistence):
850 Classe définissant le stockage d'une valeur unique entière (int) par pas.
854 def __init__(self, name="", unit="", basetype = int):
855 Persistence.__init__(self, name, unit, basetype)
857 class OneVector(Persistence):
859 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
860 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
864 def __init__(self, name="", unit="", basetype = numpy.ravel):
865 Persistence.__init__(self, name, unit, basetype)
867 class OneMatrice(Persistence):
869 Classe de stockage d'une matrice de valeurs homogènes par pas.
873 def __init__(self, name="", unit="", basetype = numpy.array):
874 Persistence.__init__(self, name, unit, basetype)
876 class OneMatrix(Persistence):
878 Classe de stockage d'une matrice de valeurs homogènes par pas.
882 def __init__(self, name="", unit="", basetype = numpy.matrix):
883 Persistence.__init__(self, name, unit, basetype)
885 class OneList(Persistence):
887 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
888 pas utiliser cette classe pour des données numériques homogènes, mais
893 def __init__(self, name="", unit="", basetype = list):
894 Persistence.__init__(self, name, unit, basetype)
897 "Fonction transparente, sans effet sur son argument"
900 class OneNoType(Persistence):
902 Classe de stockage d'un objet sans modification (cast) de type. Attention,
903 selon le véritable type de l'objet stocké à chaque pas, les opérations
904 arithmétiques à base de numpy peuvent être invalides ou donner des
905 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
906 volontairement, et pas du tout par défaut.
910 def __init__(self, name="", unit="", basetype = NoType):
911 Persistence.__init__(self, name, unit, basetype)
913 # ==============================================================================
914 class CompositePersistence(object):
916 Structure de stockage permettant de rassembler plusieurs objets de
919 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
922 __slots__ = ("__name", "__StoredObjects")
924 def __init__(self, name="", defaults=True):
928 La gestion interne des données est exclusivement basée sur les
929 variables initialisées ici (qui ne sont pas accessibles depuis
930 l'extérieur des objets comme des attributs) :
931 __StoredObjects : objets de type persistence collectés dans cet objet
933 self.__name = str(name)
935 self.__StoredObjects = {}
937 # Definition des objets par defaut
938 # --------------------------------
940 self.__StoredObjects["Informations"] = OneNoType("Informations")
941 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
942 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
943 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
944 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
945 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
946 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
947 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
948 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
949 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
950 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
951 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
952 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
955 def store(self, name=None, value=None, **kwargs):
957 Stockage d'une valeur "value" pour le "step" dans la variable "name".
959 if name is None: raise ValueError("Storable object name is required for storage.")
960 if name not in self.__StoredObjects.keys():
961 raise ValueError("No such name '%s' exists in storable objects."%name)
962 self.__StoredObjects[name].store( value=value, **kwargs )
964 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
966 Ajoute dans les objets stockables un nouvel objet défini par son nom,
967 son type de Persistence et son type de base à chaque pas.
969 if name is None: raise ValueError("Object name is required for adding an object.")
970 if name in self.__StoredObjects.keys():
971 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
973 self.__StoredObjects[name] = persistenceType( name=str(name) )
975 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
977 def get_object(self, name=None ):
979 Renvoie l'objet de type Persistence qui porte le nom demandé.
981 if name is None: raise ValueError("Object name is required for retrieving an object.")
982 if name not in self.__StoredObjects.keys():
983 raise ValueError("No such name '%s' exists in stored objects."%name)
984 return self.__StoredObjects[name]
986 def set_object(self, name=None, objet=None ):
988 Affecte directement un 'objet' qui porte le nom 'name' demandé.
989 Attention, il n'est pas effectué de vérification sur le type, qui doit
990 comporter les méthodes habituelles de Persistence pour que cela
993 if name is None: raise ValueError("Object name is required for setting an object.")
994 if name in self.__StoredObjects.keys():
995 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
996 self.__StoredObjects[name] = objet
998 def del_object(self, name=None ):
1000 Supprime un objet de la liste des objets stockables.
1002 if name is None: raise ValueError("Object name is required for retrieving an object.")
1003 if name not in self.__StoredObjects.keys():
1004 raise ValueError("No such name '%s' exists in stored objects."%name)
1005 del self.__StoredObjects[name]
1007 # ---------------------------------------------------------
1008 # Méthodes d'accès de type dictionnaire
1009 def __getitem__(self, name=None ):
1010 "x.__getitem__(y) <==> x[y]"
1011 return self.get_object( name )
1013 def __setitem__(self, name=None, objet=None ):
1014 "x.__setitem__(i, y) <==> x[i]=y"
1015 self.set_object( name, objet )
1018 "D.keys() -> list of D's keys"
1019 return self.get_stored_objects(hideVoidObjects = False)
1022 "D.values() -> list of D's values"
1023 return self.__StoredObjects.values()
1026 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
1027 return self.__StoredObjects.items()
1029 # ---------------------------------------------------------
1030 def get_stored_objects(self, hideVoidObjects = False):
1031 "Renvoie la liste des objets présents"
1032 objs = self.__StoredObjects.keys()
1037 if len(self.__StoredObjects[k]) > 0: usedObjs.append( k )
1044 # ---------------------------------------------------------
1045 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
1047 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
1048 et renvoi le nom du fichier
1050 if filename is None:
1051 if compress == "gzip":
1052 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
1053 elif compress == "bzip2":
1054 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
1056 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1058 filename = os.path.abspath( filename )
1060 if mode == "pickle":
1061 if compress == "gzip":
1062 output = gzip.open( filename, 'wb')
1063 elif compress == "bzip2":
1064 output = bz2.BZ2File( filename, 'wb')
1066 output = open( filename, 'wb')
1067 pickle.dump(self, output)
1070 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1074 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1076 Recharge un objet composite sauvé en fichier
1078 if filename is None:
1079 raise ValueError("A file name if requested to load a composite.")
1081 filename = os.path.abspath( filename )
1083 if mode == "pickle":
1084 if compress == "gzip":
1085 pkl_file = gzip.open( filename, 'rb')
1086 elif compress == "bzip2":
1087 pkl_file = bz2.BZ2File( filename, 'rb')
1089 pkl_file = open(filename, 'rb')
1090 output = pickle.load(pkl_file)
1091 for k in output.keys():
1094 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1098 # ==============================================================================
1099 if __name__ == "__main__":
1100 print("\n AUTODIAGNOSTIC\n")