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, order, osync, dovar in self.__dataobservers:
104 if __step in scheduler:
105 if order is None or dovar is None:
106 hook( self, parameters )
108 if not isinstance(order, (list, tuple)):
110 if not isinstance(dovar, dict):
112 if not bool(osync): # Async observation
113 hook( self, parameters, order, dovar )
114 else: # Sync observations
116 if len(dovar[v]) != len(self):
119 hook( self, parameters, order, dovar )
121 def pop(self, item=None):
123 Retire une valeur enregistrée par son index de stockage. Sans argument,
124 retire le dernier objet enregistre.
128 self.__values.pop(__index)
129 self.__tags.pop(__index)
136 Renvoie la taille sous forme numpy du dernier objet stocké. Si c'est un
137 objet numpy, renvoie le shape. Si c'est un entier, un flottant, un
138 complexe, renvoie 1. Si c'est une liste ou un dictionnaire, renvoie la
139 longueur. Par défaut, renvoie 1.
141 if len(self.__values) > 0:
142 if self.__basetype in [numpy.matrix, numpy.ndarray, numpy.array, numpy.ravel]:
143 return self.__values[-1].shape
144 elif self.__basetype in [int, float]:
146 elif self.__basetype in [list, dict]:
147 return (len(self.__values[-1]),)
151 raise ValueError("Object has no shape before its first storage")
153 # ---------------------------------------------------------
155 "x.__str__() <==> str(x)"
156 msg = " Index Value Tags\n"
157 for iv, vv in enumerate(self.__values):
158 msg += " i=%05i %10s %s\n"%(iv, vv, self.__tags[iv])
162 "x.__len__() <==> len(x)"
163 return len(self.__values)
168 def __getitem__(self, index=None ):
169 "x.__getitem__(y) <==> x[y]"
170 return copy.copy(self.__values[index])
172 def count(self, value):
173 "L.count(value) -> integer -- return number of occurrences of value"
174 return self.__values.count(value)
176 def index(self, value, start=0, stop=None):
177 "L.index(value, [start, [stop]]) -> integer -- return first index of value."
179 stop = len(self.__values)
180 return self.__values.index(value, start, stop)
182 # ---------------------------------------------------------
183 def __filteredIndexes(self, **kwargs):
184 "Function interne filtrant les index"
185 __indexOfFilteredItems = range(len(self.__tags))
186 __filteringKwTags = kwargs.keys()
187 if len(__filteringKwTags) > 0:
188 for tagKey in __filteringKwTags:
190 for i in __indexOfFilteredItems:
191 if tagKey in self.__tags[i]:
192 if self.__tags[i][tagKey] == kwargs[tagKey]:
194 elif isinstance(kwargs[tagKey], (list, tuple)) and self.__tags[i][tagKey] in kwargs[tagKey]:
196 __indexOfFilteredItems = __tmp
197 if len(__indexOfFilteredItems) == 0:
199 return __indexOfFilteredItems
201 # ---------------------------------------------------------
202 def values(self, **kwargs):
203 "D.values() -> list of D's values"
204 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
205 return [self.__values[i] for i in __indexOfFilteredItems]
207 def keys(self, keyword=None, **kwargs):
208 "D.keys() -> list of D's keys"
209 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
211 for i in __indexOfFilteredItems:
212 if keyword in self.__tags[i]:
213 __keys.append( self.__tags[i][keyword] )
215 __keys.append( None )
218 def items(self, keyword=None, **kwargs):
219 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
220 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
222 for i in __indexOfFilteredItems:
223 if keyword in self.__tags[i]:
224 __pairs.append( (self.__tags[i][keyword], self.__values[i]) )
226 __pairs.append( (None, self.__values[i]) )
230 "D.tagkeys() -> list of D's tag keys"
232 for dicotags in self.__tags:
233 __allKeys.extend( list(dicotags.keys()) )
234 __allKeys = sorted(set(__allKeys))
237 # def valueserie(self, item=None, allSteps=True, **kwargs):
238 # if item is not None:
239 # return self.__values[item]
241 # __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
242 # if not allSteps and len(__indexOfFilteredItems) > 0:
243 # return self.__values[__indexOfFilteredItems[0]]
245 # return [self.__values[i] for i in __indexOfFilteredItems]
247 def tagserie(self, item=None, withValues=False, outputTag=None, **kwargs):
248 "D.tagserie() -> list of D's tag serie"
250 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
252 __indexOfFilteredItems = [item,]
254 # Dans le cas où la sortie donne les valeurs d'un "outputTag"
255 if outputTag is not None and isinstance(outputTag, str):
257 for index in __indexOfFilteredItems:
258 if outputTag in self.__tags[index].keys():
259 outputValues.append( self.__tags[index][outputTag] )
260 outputValues = sorted(set(outputValues))
263 # Dans le cas où la sortie donne les tags satisfaisants aux conditions
266 return [self.__tags[index] for index in __indexOfFilteredItems]
269 for index in __indexOfFilteredItems:
270 allTags.update( self.__tags[index] )
271 allKeys = sorted(allTags.keys())
274 # ---------------------------------------------------------
276 def stepnumber(self):
278 return len(self.__values)
281 def stepserie(self, **kwargs):
282 "Nombre de pas filtrés"
283 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
284 return __indexOfFilteredItems
287 def steplist(self, **kwargs):
288 "Nombre de pas filtrés"
289 __indexOfFilteredItems = self.__filteredIndexes(**kwargs)
290 return list(__indexOfFilteredItems)
292 # ---------------------------------------------------------
295 Renvoie la série contenant, à chaque pas, la valeur moyenne des données
296 au pas. Il faut que le type de base soit compatible avec les types
300 __sr = [numpy.mean(item, dtype=mfp).astype('float') for item in self.__values]
302 raise TypeError("Base type is incompatible with numpy")
303 return( numpy.array(__sr).tolist() )
305 def stds(self, ddof=0):
307 Renvoie la série contenant, à chaque pas, l'écart-type des données
308 au pas. Il faut que le type de base soit compatible avec les types
311 ddof : c'est le nombre de degrés de liberté pour le calcul de
312 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
315 if numpy.version.version >= '1.1.0':
316 __sr = [numpy.array(item).std(ddof=ddof, dtype=mfp).astype('float') for item in self.__values]
318 return [numpy.array(item).std(dtype=mfp).astype('float') for item in self.__values]
320 raise TypeError("Base type is incompatible with numpy")
321 return( numpy.array(__sr).tolist() )
325 Renvoie la série contenant, à chaque pas, la somme des données au pas.
326 Il faut que le type de base soit compatible avec les types élémentaires
330 __sr = [numpy.array(item).sum() for item in self.__values]
332 raise TypeError("Base type is incompatible with numpy")
333 return( numpy.array(__sr).tolist() )
337 Renvoie la série contenant, à chaque pas, le minimum des données au pas.
338 Il faut que le type de base soit compatible avec les types élémentaires
342 __sr = [numpy.array(item).min() for item in self.__values]
344 raise TypeError("Base type is incompatible with numpy")
345 return( numpy.array(__sr).tolist() )
349 Renvoie la série contenant, à chaque pas, la maximum des données au pas.
350 Il faut que le type de base soit compatible avec les types élémentaires
354 __sr = [numpy.array(item).max() for item in self.__values]
356 raise TypeError("Base type is incompatible with numpy")
357 return( numpy.array(__sr).tolist() )
359 def powers(self, x2):
361 Renvoie la série contenant, à chaque pas, la puissance "**x2" au pas.
362 Il faut que le type de base soit compatible avec les types élémentaires
366 __sr = [numpy.power(item, x2) for item in self.__values]
368 raise TypeError("Base type is incompatible with numpy")
369 return( numpy.array(__sr).tolist() )
371 def norms(self, _ord=None):
373 Norm (_ord : voir numpy.linalg.norm)
375 Renvoie la série contenant, à chaque pas, la norme des données au pas.
376 Il faut que le type de base soit compatible avec les types élémentaires
380 __sr = [numpy.linalg.norm(item, _ord) for item in self.__values]
382 raise TypeError("Base type is incompatible with numpy")
383 return( numpy.array(__sr).tolist() )
385 def traces(self, offset=0):
389 Renvoie la série contenant, à chaque pas, la trace (avec l'offset) des
390 données au pas. Il faut que le type de base soit compatible avec les
391 types élémentaires numpy.
394 __sr = [numpy.trace(item, offset, dtype=mfp).astype('float') for item in self.__values]
396 raise TypeError("Base type is incompatible with numpy")
397 return( numpy.array(__sr).tolist() )
399 def maes(self, _predictor=None):
401 Mean Absolute Error (MAE)
402 mae(dX) = 1/n sum(dX_i)
404 Renvoie la série contenant, à chaque pas, la MAE des données au pas.
405 Il faut que le type de base soit compatible avec les types élémentaires
406 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
407 prédicteur est None, sinon c'est appliqué à l'écart entre les données
408 au pas et le prédicteur au même pas.
410 if _predictor is None:
412 __sr = [numpy.mean(numpy.abs(item)) 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 __sr = [numpy.mean(numpy.abs(numpy.ravel(item) - numpy.ravel(_predictor[i]))) for i, item in enumerate(self.__values)]
424 raise TypeError("Base type is incompatible with numpy")
425 return( numpy.array(__sr).tolist() )
427 def mses(self, _predictor=None):
429 Mean-Square Error (MSE) ou Mean-Square Deviation (MSD)
430 mse(dX) = 1/n sum(dX_i**2)
432 Renvoie la série contenant, à chaque pas, la MSE des données au pas. Il
433 faut que le type de base soit compatible avec les types élémentaires
434 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
435 prédicteur est None, sinon c'est appliqué à l'écart entre les données
436 au pas et le prédicteur au même pas.
438 if _predictor is None:
440 __n = self.shape()[0]
441 __sr = [(numpy.linalg.norm(item)**2 / __n) for item in self.__values]
443 raise TypeError("Base type is incompatible with numpy")
445 if len(_predictor) != len(self.__values):
446 raise ValueError("Predictor number of steps is incompatible with the values")
447 for i, item in enumerate(self.__values):
448 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
449 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
451 __n = self.shape()[0]
452 __sr = [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i]))**2 / __n) for i, item in enumerate(self.__values)]
454 raise TypeError("Base type is incompatible with numpy")
455 return( numpy.array(__sr).tolist() )
457 msds = mses # Mean-Square Deviation (MSD=MSE)
459 def rmses(self, _predictor=None):
461 Root-Mean-Square Error (RMSE) ou Root-Mean-Square Deviation (RMSD)
462 rmse(dX) = sqrt( 1/n sum(dX_i**2) ) = sqrt( mse(dX) )
464 Renvoie la série contenant, à chaque pas, la RMSE des données au pas.
465 Il faut que le type de base soit compatible avec les types élémentaires
466 numpy. C'est réservé aux variables d'écarts ou d'incréments si le
467 prédicteur est None, sinon c'est appliqué à l'écart entre les données
468 au pas et le prédicteur au même pas.
470 if _predictor is None:
472 __n = self.shape()[0]
473 __sr = [(numpy.linalg.norm(item) / math.sqrt(__n)) for item in self.__values]
475 raise TypeError("Base type is incompatible with numpy")
477 if len(_predictor) != len(self.__values):
478 raise ValueError("Predictor number of steps is incompatible with the values")
479 for i, item in enumerate(self.__values):
480 if numpy.asarray(_predictor[i]).size != numpy.asarray(item).size:
481 raise ValueError("Predictor size at step %i is incompatible with the values"%i)
483 __n = self.shape()[0]
484 __sr = [(numpy.linalg.norm(numpy.ravel(item) - numpy.ravel(_predictor[i])) / math.sqrt(__n)) for i, item in enumerate(self.__values)]
486 raise TypeError("Base type is incompatible with numpy")
487 return( numpy.array(__sr).tolist() )
489 rmsds = rmses # Root-Mean-Square Deviation (RMSD=RMSE)
496 geometry = "600x400",
499 "Préparation des plots"
501 # Vérification de la disponibilité du module Gnuplot
502 if not lpi.has_gnuplot:
503 raise ImportError("The Gnuplot module is required to plot the object.")
505 # Vérification et compléments sur les paramètres d'entrée
508 __geometry = str(geometry)
509 __sizespec = (__geometry.split('+')[0]).replace('x', ',')
512 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
514 self.__g = Gnuplot.Gnuplot() # persist=1
515 self.__g('set terminal ' + Gnuplot.GnuplotOpts.default_term + ' size ' + __sizespec)
516 self.__g('set style data lines')
518 self.__g('set autoscale')
519 self.__g('set xlabel "' + str(xlabel) + '"')
520 self.__g('set ylabel "' + str(ylabel) + '"')
522 self.__ltitle = ltitle
533 geometry = "600x400",
539 Renvoie un affichage de la valeur à chaque pas, si elle est compatible
540 avec un affichage Gnuplot (donc essentiellement un vecteur). Si
541 l'argument "step" existe dans la liste des pas de stockage effectués,
542 renvoie l'affichage de la valeur stockée à ce pas "step". Si l'argument
543 "item" est correct, renvoie l'affichage de la valeur stockée au numéro
544 "item". Par défaut ou en l'absence de "step" ou "item", renvoie un
545 affichage successif de tous les pas.
548 - step : valeur du pas à afficher
549 - item : index de la valeur à afficher
550 - steps : liste unique des pas de l'axe des X, ou None si c'est
551 la numérotation par défaut
552 - title : base du titre général, qui sera automatiquement
553 complétée par la mention du pas
554 - xlabel : label de l'axe des X
555 - ylabel : label de l'axe des Y
556 - ltitle : titre associé au vecteur tracé
557 - geometry : taille en pixels de la fenêtre et position du coin haut
558 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
559 - filename : base de nom de fichier Postscript pour une sauvegarde,
560 qui est automatiquement complétée par le numéro du
561 fichier calculé par incrément simple de compteur
562 - dynamic : effectue un affichage des valeurs à chaque stockage
563 (au-delà du second). La méthode "plots" permet de
564 déclarer l'affichage dynamique, et c'est la méthode
565 "__replots" qui est utilisée pour l'effectuer
566 - persist : booléen indiquant que la fenêtre affichée sera
567 conservée lors du passage au dessin suivant
568 Par défaut, persist = False
569 - pause : booléen indiquant une pause après chaque tracé, et
571 Par défaut, pause = True
573 if not self.__dynamic:
574 self.__preplots(title, xlabel, ylabel, ltitle, geometry, persist, pause )
576 self.__dynamic = True
577 if len(self.__values) == 0:
580 # Tracé du ou des vecteurs demandés
582 if step is not None and step < len(self.__values):
584 elif item is not None and item < len(self.__values):
587 indexes = indexes + list(range(len(self.__values)))
590 for index in indexes:
591 self.__g('set title "' + str(title) + ' (pas ' + str(index) + ')"')
592 if isinstance(steps, (list, numpy.ndarray)):
595 Steps = list(range(len(self.__values[index])))
597 self.__g.plot( Gnuplot.Data( Steps, self.__values[index], title=ltitle ) )
601 stepfilename = "%s_%03i.ps"%(filename, i)
602 if os.path.isfile(stepfilename):
603 raise ValueError("Error: a file with this name \"%s\" already exists."%stepfilename)
604 self.__g.hardcopy(filename=stepfilename, color=1)
606 eval(input('Please press return to continue...\n'))
610 Affichage dans le cas du suivi dynamique de la variable
612 if self.__dynamic and len(self.__values) < 2:
615 self.__g('set title "' + str(self.__title))
616 Steps = list(range(len(self.__values)))
617 self.__g.plot( Gnuplot.Data( Steps, self.__values, title=self.__ltitle ) )
620 eval(input('Please press return to continue...\n'))
622 # ---------------------------------------------------------
623 # On pourrait aussi utiliser d'autres attributs d'un "array" comme "tofile"
626 Renvoie la moyenne sur toutes les valeurs sans tenir compte de la
627 longueur des pas. Il faut que le type de base soit compatible avec
628 les types élémentaires numpy.
631 return numpy.mean(self.__values, axis=0, dtype=mfp).astype('float')
633 raise TypeError("Base type is incompatible with numpy")
635 def std(self, ddof=0):
637 Renvoie l'écart-type de toutes les valeurs sans tenir compte de la
638 longueur des pas. Il faut que le type de base soit compatible avec
639 les types élémentaires numpy.
641 ddof : c'est le nombre de degrés de liberté pour le calcul de
642 l'écart-type, qui est dans le diviseur. Inutile avant Numpy 1.1
645 if numpy.version.version >= '1.1.0':
646 return numpy.asarray(self.__values).std(ddof=ddof, axis=0).astype('float')
648 return numpy.asarray(self.__values).std(axis=0).astype('float')
650 raise TypeError("Base type is incompatible with numpy")
654 Renvoie la somme de toutes les valeurs sans tenir compte de la
655 longueur des pas. Il faut que le type de base soit compatible avec
656 les types élémentaires numpy.
659 return numpy.asarray(self.__values).sum(axis=0)
661 raise TypeError("Base type is incompatible with numpy")
665 Renvoie le minimum de toutes les valeurs sans tenir compte de la
666 longueur des pas. Il faut que le type de base soit compatible avec
667 les types élémentaires numpy.
670 return numpy.asarray(self.__values).min(axis=0)
672 raise TypeError("Base type is incompatible with numpy")
676 Renvoie le maximum de toutes les valeurs sans tenir compte de la
677 longueur des pas. Il faut que le type de base soit compatible avec
678 les types élémentaires numpy.
681 return numpy.asarray(self.__values).max(axis=0)
683 raise TypeError("Base type is incompatible with numpy")
687 Renvoie la somme cumulée de toutes les valeurs sans tenir compte de la
688 longueur des pas. Il faut que le type de base soit compatible avec
689 les types élémentaires numpy.
692 return numpy.asarray(self.__values).cumsum(axis=0)
694 raise TypeError("Base type is incompatible with numpy")
702 geometry = "600x400",
707 Renvoie un affichage unique pour l'ensemble des valeurs à chaque pas, si
708 elles sont compatibles avec un affichage Gnuplot (donc essentiellement
709 un vecteur). Si l'argument "step" existe dans la liste des pas de
710 stockage effectués, renvoie l'affichage de la valeur stockée à ce pas
711 "step". Si l'argument "item" est correct, renvoie l'affichage de la
712 valeur stockée au numéro "item".
715 - steps : liste unique des pas de l'axe des X, ou None si c'est
716 la numérotation par défaut
717 - title : base du titre général, qui sera automatiquement
718 complétée par la mention du pas
719 - xlabel : label de l'axe des X
720 - ylabel : label de l'axe des Y
721 - ltitle : titre associé au vecteur tracé
722 - geometry : taille en pixels de la fenêtre et position du coin haut
723 gauche, au format X11 : LxH+X+Y (défaut : 600x400)
724 - filename : nom de fichier Postscript pour une sauvegarde
725 - persist : booléen indiquant que la fenêtre affichée sera
726 conservée lors du passage au dessin suivant
727 Par défaut, persist = False
728 - pause : booléen indiquant une pause après chaque tracé, et
730 Par défaut, pause = True
733 # Vérification de la disponibilité du module Gnuplot
734 if not lpi.has_gnuplot:
735 raise ImportError("The Gnuplot module is required to plot the object.")
737 # Vérification et compléments sur les paramètres d'entrée
740 if isinstance(steps, (list, numpy.ndarray)):
743 Steps = list(range(len(self.__values[0])))
744 __geometry = str(geometry)
745 __sizespec = (__geometry.split('+')[0]).replace('x', ',')
748 Gnuplot.GnuplotOpts.gnuplot_command = 'gnuplot -persist '
750 self.__g = Gnuplot.Gnuplot() # persist=1
751 self.__g('set terminal ' + Gnuplot.GnuplotOpts.default_term + ' size ' + __sizespec)
752 self.__g('set style data lines')
754 self.__g('set autoscale')
755 self.__g('set title "' + str(title) + '"')
756 self.__g('set xlabel "' + str(xlabel) + '"')
757 self.__g('set ylabel "' + str(ylabel) + '"')
759 # Tracé du ou des vecteurs demandés
760 indexes = list(range(len(self.__values)))
761 self.__g.plot( Gnuplot.Data( Steps, self.__values[indexes.pop(0)], title=ltitle + " (pas 0)" ) )
762 for index in indexes:
763 self.__g.replot( Gnuplot.Data( Steps, self.__values[index], title=ltitle + " (pas %i)"%index ) )
766 self.__g.hardcopy(filename=filename, color=1)
768 eval(input('Please press return to continue...\n'))
770 # ---------------------------------------------------------
773 Renvoie la série sous la forme d'une unique matrice avec les données au
774 pas rangées par ligne
777 return numpy.asarray(self.__values)
779 raise TypeError("Base type is incompatible with numpy")
783 Renvoie la série sous la forme d'une unique matrice avec les données au
784 pas rangées par colonne
787 return numpy.asarray(self.__values).transpose()
788 # Eqvlt: return numpy.stack([numpy.ravel(sv) for sv in self.__values], axis=1)
790 raise TypeError("Base type is incompatible with numpy")
792 # ---------------------------------------------------------
793 def setDataObserver(self, HookFunction = None, HookParameters = None, Scheduler = None, Order = None, OSync = True, DOVar = None):
795 Association à la variable d'un triplet définissant un observer.
797 Les variables Order et DOVar sont utilisées pour un observer
798 multi-variable. Le Scheduler attendu est une fréquence, une simple
799 liste d'index ou un range des index.
802 # Vérification du Scheduler
803 # -------------------------
805 if isinstance(Scheduler, int): # Considéré comme une fréquence à partir de 0
806 Schedulers = range( 0, maxiter, int(Scheduler) )
807 elif isinstance(Scheduler, range): # Considéré comme un itérateur
808 Schedulers = Scheduler
809 elif isinstance(Scheduler, (list, tuple)): # Considéré comme des index explicites
810 Schedulers = [int(i) for i in Scheduler] # Similaire à map( int, Scheduler ) # noqa: E262
811 else: # Dans tous les autres cas, activé par défaut
812 Schedulers = range( 0, maxiter )
814 # Stockage interne de l'observer dans la variable
815 # -----------------------------------------------
816 self.__dataobservers.append( [HookFunction, HookParameters, Schedulers, Order, OSync, DOVar] )
818 def removeDataObserver(self, HookFunction = None, AllObservers = False):
820 Suppression d'un observer nommé sur la variable.
822 On peut donner dans HookFunction la même fonction que lors de la
823 définition, ou un simple string qui est le nom de la fonction. Si
824 AllObservers est vrai, supprime tous les observers enregistrés.
826 if hasattr(HookFunction, "func_name"):
827 name = str( HookFunction.func_name )
828 elif hasattr(HookFunction, "__name__"):
829 name = str( HookFunction.__name__ )
830 elif isinstance(HookFunction, str):
831 name = str( HookFunction )
837 for [hf, _, _, _, _, _] in self.__dataobservers:
839 if name is hf.__name__ or AllObservers:
840 index_to_remove.append( ih )
841 index_to_remove.reverse()
842 for ih in index_to_remove:
843 self.__dataobservers.pop( ih )
844 return len(index_to_remove)
846 def hasDataObserver(self):
847 return bool(len(self.__dataobservers) > 0)
849 # ==============================================================================
850 class SchedulerTrigger(object):
852 Classe générale d'interface de type Scheduler/Trigger
857 simplifiedCombo = None,
859 endTime = int( 1e9 ),
865 # ==============================================================================
866 class OneScalar(Persistence):
868 Classe définissant le stockage d'une valeur unique réelle (float) par pas.
870 Le type de base peut être changé par la méthode "basetype", mais il faut que
871 le nouveau type de base soit compatible avec les types par éléments de
872 numpy. On peut même utiliser cette classe pour stocker des vecteurs/listes
873 ou des matrices comme dans les classes suivantes, mais c'est déconseillé
874 pour conserver une signification claire des noms.
878 def __init__(self, name="", unit="", basetype = float):
879 Persistence.__init__(self, name, unit, basetype)
881 class OneIndex(Persistence):
883 Classe définissant le stockage d'une valeur unique entière (int) par pas.
887 def __init__(self, name="", unit="", basetype = int):
888 Persistence.__init__(self, name, unit, basetype)
890 class OneVector(Persistence):
892 Classe de stockage d'une liste de valeurs numériques homogènes par pas. Ne
893 pas utiliser cette classe pour des données hétérogènes, mais "OneList".
897 def __init__(self, name="", unit="", basetype = numpy.ravel):
898 Persistence.__init__(self, name, unit, basetype)
900 class OneMatrice(Persistence):
902 Classe de stockage d'une matrice de valeurs homogènes par pas.
906 def __init__(self, name="", unit="", basetype = numpy.array):
907 Persistence.__init__(self, name, unit, basetype)
909 class OneMatrix(Persistence):
911 Classe de stockage d'une matrice de valeurs homogènes par pas.
915 def __init__(self, name="", unit="", basetype = numpy.matrix):
916 Persistence.__init__(self, name, unit, basetype)
918 class OneList(Persistence):
920 Classe de stockage d'une liste de valeurs hétérogènes (list) par pas. Ne
921 pas utiliser cette classe pour des données numériques homogènes, mais
926 def __init__(self, name="", unit="", basetype = list):
927 Persistence.__init__(self, name, unit, basetype)
930 "Fonction transparente, sans effet sur son argument"
933 class OneNoType(Persistence):
935 Classe de stockage d'un objet sans modification (cast) de type. Attention,
936 selon le véritable type de l'objet stocké à chaque pas, les opérations
937 arithmétiques à base de numpy peuvent être invalides ou donner des
938 résultats inattendus. Cette classe n'est donc à utiliser qu'à bon escient
939 volontairement, et pas du tout par défaut.
943 def __init__(self, name="", unit="", basetype = NoType):
944 Persistence.__init__(self, name, unit, basetype)
946 # ==============================================================================
947 class CompositePersistence(object):
949 Structure de stockage permettant de rassembler plusieurs objets de
952 Des objets par défaut sont prévus, et des objets supplémentaires peuvent
955 __slots__ = ("__name", "__StoredObjects")
957 def __init__(self, name="", defaults=True):
961 La gestion interne des données est exclusivement basée sur les
962 variables initialisées ici (qui ne sont pas accessibles depuis
963 l'extérieur des objets comme des attributs) :
964 __StoredObjects : objets de type persistence collectés dans cet objet
966 self.__name = str(name)
968 self.__StoredObjects = {}
970 # Definition des objets par defaut
971 # --------------------------------
973 self.__StoredObjects["Informations"] = OneNoType("Informations")
974 self.__StoredObjects["Background"] = OneVector("Background", basetype=numpy.array)
975 self.__StoredObjects["BackgroundError"] = OneMatrix("BackgroundError")
976 self.__StoredObjects["Observation"] = OneVector("Observation", basetype=numpy.array)
977 self.__StoredObjects["ObservationError"] = OneMatrix("ObservationError")
978 self.__StoredObjects["Analysis"] = OneVector("Analysis", basetype=numpy.array)
979 self.__StoredObjects["AnalysisError"] = OneMatrix("AnalysisError")
980 self.__StoredObjects["Innovation"] = OneVector("Innovation", basetype=numpy.array)
981 self.__StoredObjects["KalmanGainK"] = OneMatrix("KalmanGainK")
982 self.__StoredObjects["OperatorH"] = OneMatrix("OperatorH")
983 self.__StoredObjects["RmsOMA"] = OneScalar("RmsOMA")
984 self.__StoredObjects["RmsOMB"] = OneScalar("RmsOMB")
985 self.__StoredObjects["RmsBMA"] = OneScalar("RmsBMA")
988 def store(self, name=None, value=None, **kwargs):
990 Stockage d'une valeur "value" pour le "step" dans la variable "name".
993 raise ValueError("Storable object name is required for storage.")
994 if name not in self.__StoredObjects.keys():
995 raise ValueError("No such name '%s' exists in storable objects."%name)
996 self.__StoredObjects[name].store( value=value, **kwargs )
998 def add_object(self, name=None, persistenceType=Persistence, basetype=None ):
1000 Ajoute dans les objets stockables un nouvel objet défini par son nom,
1001 son type de Persistence et son type de base à chaque pas.
1004 raise ValueError("Object name is required for adding 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 if basetype is None:
1008 self.__StoredObjects[name] = persistenceType( name=str(name) )
1010 self.__StoredObjects[name] = persistenceType( name=str(name), basetype=basetype )
1012 def get_object(self, name=None ):
1014 Renvoie l'objet de type Persistence qui porte le nom demandé.
1017 raise ValueError("Object name is required for retrieving an object.")
1018 if name not in self.__StoredObjects.keys():
1019 raise ValueError("No such name '%s' exists in stored objects."%name)
1020 return self.__StoredObjects[name]
1022 def set_object(self, name=None, objet=None ):
1024 Affecte directement un 'objet' qui porte le nom 'name' demandé.
1025 Attention, il n'est pas effectué de vérification sur le type, qui doit
1026 comporter les méthodes habituelles de Persistence pour que cela
1030 raise ValueError("Object name is required for setting an object.")
1031 if name in self.__StoredObjects.keys():
1032 raise ValueError("An object with the same name '%s' already exists in storable objects. Choose another one."%name)
1033 self.__StoredObjects[name] = objet
1035 def del_object(self, name=None ):
1037 Supprime un objet de la liste des objets stockables.
1040 raise ValueError("Object name is required for retrieving an object.")
1041 if name not in self.__StoredObjects.keys():
1042 raise ValueError("No such name '%s' exists in stored objects."%name)
1043 del self.__StoredObjects[name]
1045 # ---------------------------------------------------------
1046 # Méthodes d'accès de type dictionnaire
1047 def __getitem__(self, name=None ):
1048 "x.__getitem__(y) <==> x[y]"
1049 return self.get_object( name )
1051 def __setitem__(self, name=None, objet=None ):
1052 "x.__setitem__(i, y) <==> x[i]=y"
1053 self.set_object( name, objet )
1056 "D.keys() -> list of D's keys"
1057 return self.get_stored_objects(hideVoidObjects = False)
1060 "D.values() -> list of D's values"
1061 return self.__StoredObjects.values()
1064 "D.items() -> list of D's (key, value) pairs, as 2-tuples"
1065 return self.__StoredObjects.items()
1067 # ---------------------------------------------------------
1068 def get_stored_objects(self, hideVoidObjects = False):
1069 "Renvoie la liste des objets présents"
1070 objs = self.__StoredObjects.keys()
1075 if len(self.__StoredObjects[k]) > 0:
1076 usedObjs.append( k )
1083 # ---------------------------------------------------------
1084 def save_composite(self, filename=None, mode="pickle", compress="gzip"):
1086 Enregistre l'objet dans le fichier indiqué selon le "mode" demandé,
1087 et renvoi le nom du fichier
1089 if filename is None:
1090 if compress == "gzip":
1091 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.gz"
1092 elif compress == "bzip2":
1093 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl.bz2"
1095 filename = os.tempnam( os.getcwd(), 'dacp' ) + ".pkl"
1097 filename = os.path.abspath( filename )
1099 if mode == "pickle":
1100 if compress == "gzip":
1101 output = gzip.open( filename, 'wb')
1102 elif compress == "bzip2":
1103 output = bz2.BZ2File( filename, 'wb')
1105 output = open( filename, 'wb')
1106 pickle.dump(self, output)
1109 raise ValueError("Save mode '%s' unknown. Choose another one."%mode)
1113 def load_composite(self, filename=None, mode="pickle", compress="gzip"):
1115 Recharge un objet composite sauvé en fichier
1117 if filename is None:
1118 raise ValueError("A file name if requested to load a composite.")
1120 filename = os.path.abspath( filename )
1122 if mode == "pickle":
1123 if compress == "gzip":
1124 pkl_file = gzip.open( filename, 'rb')
1125 elif compress == "bzip2":
1126 pkl_file = bz2.BZ2File( filename, 'rb')
1128 pkl_file = open(filename, 'rb')
1129 output = pickle.load(pkl_file)
1130 for k in output.keys():
1133 raise ValueError("Load mode '%s' unknown. Choose another one."%mode)
1137 # ==============================================================================
1138 if __name__ == "__main__":
1139 print("\n AUTODIAGNOSTIC\n")