]> SALOME platform Git repositories - tools/eficas.git/blob - Aster/Cata/Utilitai/Table.py
Salome HOME
CCAR: merge de la version 1.14 dans la branche principale
[tools/eficas.git] / Aster / Cata / Utilitai / Table.py
1 #@ MODIF Table Utilitai  DATE 16/10/2007   AUTEUR REZETTE C.REZETTE 
2 # -*- coding: iso-8859-1 -*-
3 #            CONFIGURATION MANAGEMENT OF EDF VERSION
4 # ======================================================================
5 # COPYRIGHT (C) 1991 - 2004  EDF R&D                  WWW.CODE-ASTER.ORG
6 # THIS PROGRAM IS FREE SOFTWARE; YOU CAN REDISTRIBUTE IT AND/OR MODIFY  
7 # IT UNDER THE TERMS OF THE GNU GENERAL PUBLIC LICENSE AS PUBLISHED BY  
8 # THE FREE SOFTWARE FOUNDATION; EITHER VERSION 2 OF THE LICENSE, OR     
9 # (AT YOUR OPTION) ANY LATER VERSION.                                                  
10 #                                                                       
11 # THIS PROGRAM IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT   
12 # WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF            
13 # MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. SEE THE GNU      
14 # GENERAL PUBLIC LICENSE FOR MORE DETAILS.                              
15 #                                                                       
16 # YOU SHOULD HAVE RECEIVED A COPY OF THE GNU GENERAL PUBLIC LICENSE     
17 # ALONG WITH THIS PROGRAM; IF NOT, WRITE TO EDF R&D CODE_ASTER,         
18 #    1 AVENUE DU GENERAL DE GAULLE, 92141 CLAMART CEDEX, FRANCE.        
19 # ======================================================================
20
21 # RESPONSABLE MCOURTOI M.COURTOIS
22 __all__ = ['Table', 'merge']
23
24 import sys
25 import re
26 from copy  import copy
27
28 from types import ListType, TupleType, IntType, LongType, FloatType, ComplexType, \
29                   DictType, StringType, StringTypes, UnicodeType, NoneType
30 EnumTypes = (ListType, TupleType)
31 NumberTypes = (IntType, LongType, FloatType, ComplexType)
32
33 import transpose
34 from Macro.externe_mess import UTMESS
35
36 if not sys.modules.has_key('Graph'):
37    try:
38       from Utilitai import Graph
39    except ImportError:
40       import Graph
41
42 # formats de base (identiques à ceux du module Graph)
43 DicForm = {
44    'csep'  : ' ',       # séparateur
45    'ccom'  : '#',       # commentaire
46    'cdeb'  : '',        # début de ligne
47    'cfin'  : '\n',      # fin de ligne
48    'sepch' : ';',       # séparateur entre deux lignes d'une cellule
49    'formK' : '%-8s',    # chaines
50    'formR' : '%12.5E',  # réels
51    'formI' : '%8d'      # entiers
52 }
53 # type par défaut des chaines de caractères
54 Kdef = 'K24'
55
56 # ------------------------------------------------------------------------------
57 # ------------------------------------------------------------------------------
58 # ------------------------------------------------------------------------------
59 class TableBase(object):
60    """Classe pour partager les méthodes d'impression entre Table et Colonne
61    (c'est surtout utile pour vérifier que l'extraction et les filtres sur les
62    colonnes sont corrects).
63    """
64    def __init__(self):
65       """Constructeur.
66       """
67       self.rows=None
68       self.para=None
69       self.type=None
70       self.titr=None
71    
72    def __repr__(self):
73       return self.ReprTable()
74    def Croise(self, **kargs):
75       raise NotImplementedError, 'Must be defined in a derived class'
76
77    def __len__(self):
78       """Retourne le nombre de ligne dans la Table/Colonne.
79       """
80       return len(self.rows)
81
82 # ------------------------------------------------------------------------------
83    def Impr(self, FICHIER=None, FORMAT='TABLEAU', dform=None, **opts):
84       """Impresssion de la Table selon le format spécifié.
85          FICHIER : nom du(des) fichier(s). Si None, on dirige vers stdout
86          dform : dictionnaire de formats d'impression (format des réels,
87             commentaires, saut de ligne...)
88          opts  : selon FORMAT.
89       """
90       para={
91          'TABLEAU'         : { 'mode' : 'a', 'driver' : self.ImprTableau,   },
92          'ASTER'           : { 'mode' : 'a', 'driver' : self.ImprTableau,   },
93          'XMGRACE'         : { 'mode' : 'a', 'driver' : self.ImprGraph,     },
94          'AGRAF'           : { 'mode' : 'a', 'driver' : self.ImprTableau,   },
95          'TABLEAU_CROISE'  : { 'mode' : 'a', 'driver' : self.ImprTabCroise, },
96       }
97       kargs={
98          'FICHIER'   : FICHIER,
99          'FORMAT'    : FORMAT,
100          'dform'     : DicForm.copy(),
101          'mode'      : para[FORMAT]['mode'],
102       }
103       if dform != None and type(dform) == DictType:
104          kargs['dform'].update(dform)
105       # ajout des options
106       kargs.update(opts)
107       
108       if not kargs.get('PAGINATION'):
109          # call the associated driver
110          para[FORMAT]['driver'](**kargs)
111
112       else:
113          if not type(kargs['PAGINATION']) in EnumTypes:
114             ppag = [kargs['PAGINATION'],]
115          else:
116             ppag = list(kargs['PAGINATION'])
117          del kargs['PAGINATION']
118          npag = len(ppag)
119          # paramètres hors ceux de la pagination
120          lkeep = [p for p in self.para if ppag.count(p)==0]
121          # création des listes des valeurs distinctes
122          lvd = []
123          for p in ppag:
124             lvp = getattr(self,p).values()
125             lvn = []
126             for it in lvp:
127                if it != None and lvn.count(it) == 0:
128                   lvn.append(it)
129             lvn.sort()
130             lvd.append(lvn)
131          # création des n-uplets
132          s  = '[['+','.join(['x'+str(i) for i in range(npag)])+'] '
133          s += ' '.join(['for x'+str(i)+' in lvd['+str(i)+']' for i in range(npag)])+']'
134          try:
135             lnup = eval(s)
136          except SyntaxError, s:
137             UTMESS('F','Table','Erreur lors de la construction des n-uplets')
138          # pour chaque n-uplet, on imprime la sous-table
139          for nup in lnup:
140             tab = self
141             for i in range(npag):
142                tab = tab & (getattr(tab,ppag[i]) == nup[i])
143                sl = ''
144                if tab.titr: sl='\n'
145                tab.titr += sl+ppag[i]+': '+str(nup[i])
146             tab[lkeep].Impr(**kargs)
147
148 # ------------------------------------------------------------------------------
149    def ImprTableau(self,**kargs):
150       """Impression au format TABLEAU ou ASTER
151       """
152       # fichier ou stdout
153       if kargs.get('FICHIER')<>None:
154          f=open(kargs['FICHIER'],kargs['mode'])
155       else:
156          f=sys.stdout
157       # ecriture
158       f.write(self.ReprTable(**kargs) + '\n')
159       # fermeture
160       if kargs.get('FICHIER')<>None:
161          f.close()
162
163 # ------------------------------------------------------------------------------
164    def ReprTable(self,FORMAT='TABLEAU',dform=None,**ignore):
165       """Représentation d'une Table ou d'une Colonne sous forme d'un tableau.
166       """
167       rows=self.rows
168       para=self.para
169       typ =self.type
170       if not type(para) in EnumTypes:
171          para=[self.para,]
172          typ =[self.type,]
173       if dform==None:
174          dform = DicForm.copy()
175       # est-ce que l'attribut .type est renseigné ?
176       typdef=typ<>[None]*len(typ)
177       txt=[]
178       # ['']+ pour ajouter un séparateur en début de ligne
179       lspa=['',]
180       # lmax : largeur max des colonnes = max(form{K,R,I},len(parametre))
181       lmax=[]
182       for p in para:
183          t=typ[para.index(p)]
184          larg_max=max([len(str(p))] + \
185                [len(FMT(dform,k,t) % 0) for k in ('formK','formR','formI')])
186          lspa.append(FMT(dform,'formK',t,larg_max,str(p)) % p)
187          lmax.append(larg_max)
188       if typdef:
189          stype=dform['csep'].join([''] + \
190           [FMT(dform,'formK',typ[i],lmax[i]) % typ[i] for i in range(len(para))])
191       txt.append(dform['ccom'])
192       txt.append(dform['ccom']+'-'*80)
193       txt.append(dform['ccom'])
194       ASTER=(FORMAT=='ASTER')
195       if ASTER:
196          txt.append('#DEBUT_TABLE')
197       if self.titr:
198          if ASTER:
199             txt.extend(['#TITRE '+lig for lig in self.titr.split('\n')])
200          else:
201             txt.extend([dform['ccom']+lig for lig in self.titr.split('\n')])
202       txt.append(dform['csep'].join(lspa))
203       if ASTER and typdef:
204          txt.append(stype)
205       for r in rows:
206          lig=['']
207          empty=True
208          for v in para:
209             i=para.index(v)
210             t=typ[i]
211             rep=r.get(v,None)
212             if type(rep) is FloatType:
213                lig.append(FMT(dform,'formR',t,lmax[i]) % rep)
214                empty=False
215             elif type(rep) in (IntType, LongType):
216                lig.append(FMT(dform,'formI',t,lmax[i]) % rep)
217                empty=False
218             else:
219                if rep==None:
220                   rep='-'
221                else:
222                   empty=False
223                s=FMT(dform,'formK',t,lmax[i],rep) % str(rep)
224                # format AGRAF = TABLEAU + '\' devant les chaines de caractères !
225                if FORMAT=='AGRAF':
226                   s='\\'+s
227                lig.append(s)
228          if not empty:
229             lig2 = [dform['sepch'].join(ch.splitlines()) for ch in lig]
230             txt.append(dform['csep'].join(lig2))
231       if ASTER:
232          txt.append('#FIN_TABLE')
233       # ajout du debut de ligne
234       if dform['cdeb']<>'':
235          txt=[dform['cdeb']+t for t in txt]
236
237       return dform['cfin'].join(txt)
238 # ------------------------------------------------------------------------------
239    def ImprTabCroise(self,**kargs):
240       """Impression au format TABLEAU_CROISE d'une table ayant 3 paramètres.
241       """
242       # création du tableau croisé et impression au format TABLEAU
243       tabc=self.Croise()
244       kargs['FORMAT']='TABLEAU'
245       tabc.Impr(**kargs)
246 # ------------------------------------------------------------------------------
247    def ImprGraph(self, **kargs):
248       """Impression au format XMGRACE : via le module Graph
249       """
250       args=kargs.copy()
251       if len(self.para) != 2:
252          UTMESS('A','Table','La table doit avoir exactement deux paramètres '\
253                 'pour une impression au format XMGRACE.')
254          return
255       # suppression des lignes contenant une cellule vide
256       tnv = getattr(self, self.para[0]).NON_VIDE() \
257           & getattr(self, self.para[1]).NON_VIDE()
258       # objet Graph
259       graph=Graph.Graph()
260       dicC={
261          'Val' : [getattr(tnv, tnv.para[0]).values(),
262                   getattr(tnv, tnv.para[1]).values()],
263          'Lab' : tnv.para,
264       }
265       if args['LEGENDE']==None: del args['LEGENDE']
266       Graph.AjoutParaCourbe(dicC, args)
267       graph.AjoutCourbe(**dicC)
268       
269       # Surcharge des propriétés du graphique et des axes
270       # (bloc quasiment identique dans impr_fonction_ops)
271       if args.get('TITRE'):            graph.Titre=args['TITRE']
272       if args.get('BORNE_X'):
273                                        graph.Min_X=args['BORNE_X'][0]
274                                        graph.Max_X=args['BORNE_X'][1]
275       if args.get('BORNE_Y'):
276                                        graph.Min_Y=args['BORNE_Y'][0]
277                                        graph.Max_Y=args['BORNE_Y'][1]
278       if args.get('LEGENDE_X'):        graph.Legende_X=args['LEGENDE_X']
279       if args.get('LEGENDE_Y'):        graph.Legende_Y=args['LEGENDE_Y']
280       if args.get('ECHELLE_X'):        graph.Echelle_X=args['ECHELLE_X']
281       if args.get('ECHELLE_Y'):        graph.Echelle_Y=args['ECHELLE_Y']
282       if args.get('GRILLE_X'):         graph.Grille_X=args['GRILLE_X']
283       if args.get('GRILLE_Y'):         graph.Grille_Y=args['GRILLE_Y']
284       
285       try:
286          graph.Trace(**args)
287       except TypeError:
288          UTMESS('A','Table','Les cellules ne doivent contenir que des nombres réels')
289
290 # ------------------------------------------------------------------------------
291 # ------------------------------------------------------------------------------
292 # ------------------------------------------------------------------------------
293 class Table(TableBase):
294    """Une table est construite comme une liste de lignes, chaque ligne est
295    un dictionnaire.
296    On crée puis on ajoute les lignes avec la méthode append :
297       t=Table()
298       t.append(dict(a=1,b=2))
299       t.append(dict(a=3,b=4))
300    La méthode __iter__ définit un itérateur sur les lignes de la table,
301    __repr__ retourne une représentation de la table, utilisée par "print t".
302    Grace à la classe Colonne et à sa méthode _extract, il est possible
303    de construire une sous-table qui satisfait un critère donné.
304    Le critère est donné par une fonction Python qui retourne vrai
305    ou faux si la valeur d'une colonne respecte le critère ou non.
306    Exemple:
307      def critere(valeur):
308          return valeur < 10
309      soustable = t.a._extract(critere)
310    t.a retourne un objet intermédiaire de la classe Colonne qui mémorise
311    le nom de la colonne demandée (a, ici).
312    """
313 # ------------------------------------------------------------------------------
314    def __init__(self, rows=[], para=[], typ=[], titr=''):
315       """Constructeur de la Table :
316          rows : liste des lignes (dict)
317          para : liste des paramètres
318          type : liste des types des paramètres
319          titr : titre de la table
320       """
321       self.rows = [r for r in rows if r.values() != [None]*len(r.values())]
322       self.para = list(para)
323       for i in self.para :
324           if self.para.count(i) != 1 :
325              UTMESS('F','Table','Parametre en double: %s' %i)
326       if len(typ) == len(self.para):
327          self.type = list(typ)
328       else:
329          self.type = [None]*len(self.para)
330       self.titr = titr
331    
332 # ------------------------------------------------------------------------------
333    def copy(self):
334       """Retourne une copie de la table.
335       """
336       rows = []
337       for r in self.rows:
338          rows.append(copy(r))
339       return Table(rows, self.para[:], self.type[:], self.titr)
340
341 # ------------------------------------------------------------------------------
342    def append(self, obj):
343       """Ajoute une ligne (type dict) qui peut éventuellement définir un
344       nouveau paramètre."""
345       para=obj.keys()
346       for p in para:
347          if not p in self.para:
348             self.para.append(p)
349             self.type.append(_typaster(obj[p]))
350          else:
351             ip=self.para.index(p)
352             self.type[ip]=_typaster(obj[p], self.type[ip])
353       self.rows.append(obj)
354
355 # ------------------------------------------------------------------------------
356    def SansColonneVide(self):
357       """Retourne une copie de la table dans laquelle on a supprimé les colonnes
358       vides (les lignes vides sont automatiquement supprimées).
359       """
360       tab = self.copy()
361       lp = tab.para[:]
362       for para in lp:
363          if len(tab[para]) == 0:
364             bid = lp.pop(0)
365       return tab[lp]
366
367 # ------------------------------------------------------------------------------
368    def __setitem__(self, k_para, k_value):
369       """Ajoute une colonne k_para dont les valeurs sont dans k_value"""
370       if len(k_value)==0:
371          return
372       if k_para in self.para :
373          UTMESS('F','Table','(setitem) Le parametre %s existe déjà.' % k_para)
374       self.para.append(k_para)
375       self.type.append(_typaster(k_value[0]))
376       i=0
377       for row in self:
378          if i<len(k_value):
379             row[k_para]=k_value[i]
380             self.type[-1]=_typaster(k_value[i], self.type[-1])
381          else:
382             row[k_para]=None
383          i+=1
384       for j in range(i,len(k_value)): 
385          self.append({k_para:k_value[j]})
386
387 # ------------------------------------------------------------------------------
388    def fromfunction(self, nom_para, funct, l_para=None, const=None):
389       """Ajoute une colonne `nom_para` en évaluant la fonction `funct` sur
390       la valeur des paramètres `l_para` (qui doivent exister dans la table).
391       Si `l_para` n'est pas fourni, on prend `funct`.nompar (FORMULE Aster).
392       On peut passer un dictionnaire de constantes dans `const`. Quand on
393       utilise une FORMULE Aster, les constantes sont prises dans le contexte
394       global.
395       """
396       # vérif préalables
397       if not hasattr(funct, '__call__'):
398          UTMESS('F', 'Table', "(fromfunction) '%s' n'a pas d'attribut '__call__'." \
399             % funct.__name__)
400       if nom_para in self.para :
401          UTMESS('F','Table','Le parametre %s existe déjà.' % nom_para)
402       if l_para == None:
403          if not hasattr(funct, 'nompar'):
404             UTMESS('F', 'Table', "(fromfunction) '%s' n'a pas d'attribut 'nompar'." \
405                % funct.__name__)
406          l_para = funct.nompar
407       if not type(l_para) in EnumTypes:
408          l_para = [l_para]
409       not_found = ', '.join([p for p in l_para if not p in self.para])
410       if not_found != '':
411          UTMESS('F','Table','Parametre(s) absent(s) de la table : %s' % not_found)
412       if const == None:
413          const = {}
414       if type(const) is not DictType:
415          UTMESS('F', 'Table', "L'argument 'const' doit etre de type 'dict'.")
416       # liste des valeurs des paramètres
417       tabpar = []
418       for para in l_para:
419          vals = getattr(self, para).values()
420          tabpar.append(vals)
421       tabpar = transpose.transpose(tabpar)
422       # évaluation de la fonction sur ces paramètres
423       vectval = []
424       for lpar in tabpar:
425          # si un paramètre est absent, on ne peut pas évaluer la formule
426          if None in lpar:
427             vectval.append(None)
428          else:
429             vectval.append(funct(*lpar, **const))
430       # ajout de la colonne
431       self[nom_para] = vectval
432
433 # ------------------------------------------------------------------------------
434    def __iter__(self):
435       """Itère sur les lignes de la Table"""
436       return iter(self.rows)
437
438 # ------------------------------------------------------------------------------
439    def __getattr__(self, column):
440       """Construit un objet intermediaire (couple table, colonne)"""
441       typ=None
442       if not column in self.para:
443          column=''
444       else:
445          typ=self.type[self.para.index(column)]
446       return Colonne(self, column, typ)
447
448 # ------------------------------------------------------------------------------
449    def sort(self, CLES=None, ORDRE='CROISSANT'):
450       """Tri de la table.
451          CLES  : liste des clés de tri
452          ORDRE : CROISSANT ou DECROISSANT
453       """
454       # par défaut, on prend tous les paramètres
455       if CLES == None:
456          CLES = self.para[:]
457       # vérification des arguments
458       if not type(CLES) in EnumTypes:
459          CLES = [CLES]
460       else:
461          CLES = list(CLES)
462       not_found = ', '.join([p for p in CLES if not p in self.para])
463       if not_found != '':
464          UTMESS('F', 'Table', 'Parametre(s) absent(s) de la table : %s' % not_found)
465       if not ORDRE in ('CROISSANT', 'DECROISSANT'):
466          UTMESS('F', 'Table', 'Valeur incorrecte pour ORDRE : %s' % ORDRE)
467       # tri
468       self.rows = sort_table(self.rows, self.para, CLES, (ORDRE=='DECROISSANT'))
469
470 # ------------------------------------------------------------------------------
471    def __delitem__(self, args):
472       """Supprime les colonnes correspondantes aux éléments de args """
473       if not type(args) in EnumTypes:
474          args=[args,]
475       new_rows=self.rows
476       new_para=self.para
477       new_type=self.type
478       for item in args:
479          del new_type[new_para.index(item)]
480          new_para.remove(item)
481          for line in new_rows:
482             del line[item] 
483       return Table(new_rows, new_para, new_type, self.titr)
484
485 # ------------------------------------------------------------------------------
486    def __getitem__(self, args):
487       """Extrait la sous table composée des colonnes dont les paramètres sont dans args """
488       if not type(args) in EnumTypes:
489          args=[args,]
490       else:
491          args=list(args)
492       new_rows=[]
493       new_para=args
494       new_type=[]
495       for item in new_para:
496          if not item in self.para:
497             return Table()
498          new_type.append(self.type[self.para.index(item)])
499       for line in self:
500          new_line={}
501          for item in new_para:
502             new_line[item]=line.get(item)
503          new_rows.append(new_line)
504       return Table(new_rows, new_para, new_type, self.titr)
505
506 # ------------------------------------------------------------------------------
507    def __and__(self, other):
508       """Intersection de deux tables (opérateur &)"""
509       if other.para<>self.para:
510          UTMESS('A','Table','Les paramètres sont différents')
511          return Table()
512       else:
513          tmp = [ r for r in self if r in other.rows ]
514          return Table(tmp, self.para, self.type, self.titr)
515
516 # ------------------------------------------------------------------------------
517    def __or__(self, other):
518       """Union de deux tables (opérateur |)"""
519       if other.para<>self.para:
520          UTMESS('A','Table','Les paramètres sont différents')
521          return Table()
522       else:
523          tmp = self.rows[:]
524          tmp.extend([ r for r in other if r not in self ])
525          return Table(tmp, self.para, self.type[:], self.titr)
526
527 # ------------------------------------------------------------------------------
528    def values(self):
529       """Renvoie la table sous la forme d'un dictionnaire de listes dont les
530       clés sont les paramètres.
531       """
532       dico={}
533       for column in self.para:
534          dico[column]=Colonne(self, column).values()
535       return dico
536
537 # ------------------------------------------------------------------------------
538    def dict_CREA_TABLE(self):
539       """Renvoie le dictionnaire des mots-clés à fournir à la commande CREA_TABLE
540       pour produire une table_sdaster.
541       """
542       dico={ 'TITRE' : ['%-80s' % lig for lig in self.titr.split('\n')],
543              'LISTE' : [], }
544       # remplissage de chaque occurence (pour chaque paramètre) du mot-clé facteur LISTE
545       for i in range(len(self.para)):
546          # nom du paramètre et type si K*
547          d={ 'PARA' : self.para[i], }
548          typ=self.type[i]
549          if typ==None:
550             UTMESS('F', 'Table', 'Type du paramètre %s non défini.' %\
551                    self.para[i])
552          elif typ[0]=='K':
553             mc='LISTE_K'
554             if not typ in ('K8', 'K16', 'K24'):
555                UTMESS('A','Table','Type du paramètre %s forcé à %s' % (self.para[i],Kdef))
556                typ=Kdef
557             d['TYPE_K']=typ
558          elif typ=='I':
559             mc='LISTE_I'
560          elif typ=='R':
561             mc='LISTE_R'
562          # valeurs sans trou / avec trou
563          vals=getattr(self, self.para[i]).values()
564          if vals.count(None)==0:
565             d[mc]=vals
566          else:
567             d['NUME_LIGN'] = [j+1 for j in range(len(vals)) if vals[j]<>None]
568             d[mc]          = [v   for v in vals             if v      <>None]
569          if len(d[mc])==0:
570             UTMESS('I','Table','Colonne %s vide' % self.para[i])
571          else:
572             dico['LISTE'].append(d)
573       if len(dico['LISTE'])==0:
574          UTMESS('F','Table','La table est vide')
575       return dico
576
577 # ------------------------------------------------------------------------------
578    def Array(self,Para,Champ):
579       """Renvoie sous forme de NumArray le résultat d'une extraction dans une table
580       méthode utile à macr_recal
581       """
582       import Numeric
583       __Rep = self[Para,Champ].values()
584       F = Numeric.zeros((len(__Rep[Para]),2), Numeric.Float)
585       for i in range(len(__Rep[Para])):
586          F[i][0] = __Rep[Para][i]
587          F[i][1] = __Rep[Champ][i]
588       del(__Rep)
589       return F
590
591 # ------------------------------------------------------------------------------
592    def Croise(self):
593       """Retourne un tableau croisé P3(P1,P2) à partir d'une table ayant
594       trois paramètres (P1, P2, P3).
595       """
596       if len(self.para)<>3:
597          UTMESS('A', 'Table', 'La table doit avoir exactement trois paramètres.')
598          return Table()
599       py, px, pz = self.para
600       ly, lx, lz = [getattr(self,p).values() for p in self.para]
601       new_rows=[]
602       #lpz='%s=f(%s,%s)' % (pz,px,py)
603       lpz='%s/%s' % (px,py)
604       new_para=[lpz,]
605       # attention aux doublons dans lx et ly
606       for it in ly:
607          if it<>None and new_para.count(it)==0:
608             new_para.append(it)
609       newx=[]
610       for it in lx:
611          if it<>None and newx.count(it)==0:
612             newx.append(it)
613       for x in newx:
614          if x<>None:
615             d={ lpz : x, }
616             taux = (getattr(self,px)==x)
617             for dz in taux.rows:
618                d[dz[py]]=dz[pz]
619             new_rows.append(d)
620       new_type=[self.type[0],] + [self.type[2]]*len(ly)
621       new_titr=self.titr
622       if new_titr<>'': new_titr+='\n'
623       new_titr+=pz + ' FONCTION DE ' + px + ' ET ' + py
624       return Table(new_rows, new_para, new_type, new_titr)
625
626 # ------------------------------------------------------------------------------
627    def Renomme(self, pold, pnew):
628       """Renomme le paramètre `pold` en `pnew`.
629       """
630       if not pold in self.para:
631          raise KeyError, 'Paramètre %s inexistant dans cette table' % pold
632       elif self.para.count(pnew)>0:
633          raise KeyError, 'Le paramètre %s existe déjà dans la table' % pnew
634       else:
635          self.para[self.para.index(pold)] = pnew
636          for lig in self:
637             lig[pnew] = lig[pold]
638             del lig[pold]
639
640 # ------------------------------------------------------------------------------
641 # ------------------------------------------------------------------------------
642 # ------------------------------------------------------------------------------
643 class Colonne(TableBase):
644    """Classe intermédiaire pour mémoriser un couple (table, nom de colonne)
645    et exprimer les critères d'extraction sous une forme naturelle en python
646    en surchargeant les operateurs <, >, <> et =.
647    Alors on peut écrire la requete simple :
648      soustable=t.a<10
649    Ainsi que des requetes plus complexes :
650      soustable=t.a<10 and t.b <4
651    ou
652      soustable=t.a<10 or t.b <4
653    Les "alias" EQ, NE, LE, LT, GE, GT permettent à la macro IMPR_TABLE
654    d'utiliser directement le mot-clé utilisateur CRIT_COMP défini dans le
655    catalogue : getattr(Table,CRIT_COMP).
656    """
657 # ------------------------------------------------------------------------------
658    def __init__(self, table, column, typ=None):
659       """Constructeur (objet Table associé, paramètre de la colonne, type du
660       paramètre).
661       """
662       self.Table=table
663       self.rows=self.Table.rows
664       self.para=column
665       self.type=typ
666       self.titr=''
667
668 # ------------------------------------------------------------------------------
669    def _extract(self, fun):
670       """Construit une table avec les lignes de self.Table 
671          dont l'élément de nom self.para satisfait le critère fun,
672          fun est une fonction qui retourne vrai ou faux
673       """
674       return Table([row for row in self.Table if fun(row.get(self.para))], self.Table.para, self.Table.type, self.Table.titr)
675
676 # ------------------------------------------------------------------------------
677    def __le__(self, VALE):
678       if type(VALE) in EnumTypes :
679         crit = max(VALE)
680       else:
681         crit = VALE
682       return self._extract(lambda v: v<>None and v<=crit)
683
684 # ------------------------------------------------------------------------------
685    def __lt__(self, VALE):
686       if type(VALE) in EnumTypes :
687         crit = max(VALE)
688       else:
689         crit = VALE
690       return self._extract(lambda v: v<>None and v<crit)
691
692 # ------------------------------------------------------------------------------
693    def __ge__(self, VALE):
694       if type(VALE) in EnumTypes :
695         crit = min(VALE)
696       else:
697         crit = VALE
698       return self._extract(lambda v: v<>None and v>=crit)
699
700 # ------------------------------------------------------------------------------
701    def __gt__(self, VALE):
702       if type(VALE) in EnumTypes :
703         crit = min(VALE)
704       else:
705         crit = VALE
706       return self._extract(lambda v: v<>None and v>crit)
707
708 # ------------------------------------------------------------------------------
709    def __eq__(self, VALE, CRITERE='RELATIF', PRECISION=0.):
710       if not type(VALE) in EnumTypes :
711          VALE = [VALE]
712       if type(VALE[0]) in StringTypes:
713          stripVALE = [value.strip() for value in VALE]
714          return self._extract(lambda v: str(v).strip() in stripVALE)
715       else:           
716          if PRECISION==0. :
717             return self._extract(lambda v : v in VALE)
718          elif CRITERE=='ABSOLU':
719             return self._extract(lambda v : _func_test_abs(v, VALE, PRECISION))
720          else:
721             return self._extract(lambda v : _func_test_rela(v, VALE, PRECISION))
722       
723 # ------------------------------------------------------------------------------
724    def REGEXP(self, regexp):
725       """Retient les lignes dont le paramètre satisfait l'expression
726       régulière `regexp`.
727       """
728       if not type(regexp) in StringTypes:
729          return self._extract(lambda v : False)
730       return self._extract(lambda v : v != None and re.search(regexp, v) != None)
731
732 # ------------------------------------------------------------------------------
733    def __ne__(self, VALE, CRITERE='RELATIF', PRECISION=0.):
734       if not type(VALE) in EnumTypes :
735          VALE = [VALE]
736       if type(VALE[0]) in StringTypes:
737          stripVALE = [value.strip() for value in VALE]
738          return self._extract(lambda v: str(v).strip() not in stripVALE)
739       else:           
740          if PRECISION==0. :
741             return self._extract(lambda v : v not in VALE)
742          elif CRITERE=='ABSOLU':
743             return self._extract(lambda v : not (_func_test_abs(v, VALE, PRECISION)))
744          else:
745             return self._extract(lambda v : not (_func_test_rela(v, VALE, PRECISION)))
746
747 # ------------------------------------------------------------------------------
748    def MAXI(self):
749       # important pour les performances de récupérer le max une fois pour toutes
750       maxi=max(self)
751       return self._extract(lambda v: v==maxi)
752
753 # ------------------------------------------------------------------------------
754    def MINI(self):
755       # important pour les performances de récupérer le min une fois pour toutes
756       mini=min(self)
757       return self._extract(lambda v: v==mini)
758
759 # ------------------------------------------------------------------------------
760    def ABS_MAXI(self):
761       # important pour les performances de récupérer le max une fois pour toutes
762       abs_maxi=max([abs(v) for v in self.values() if type(v) in NumberTypes])
763       return self._extract(lambda v: v==abs_maxi or v==-abs_maxi)
764
765 # ------------------------------------------------------------------------------
766    def ABS_MINI(self):
767       # important pour les performances de récupérer le min une fois pour toutes
768       abs_mini=min([abs(v) for v in self.values() if type(v) in NumberTypes])
769       # tester le type de v est trop long donc pas de abs(v)
770       return self._extract(lambda v: v==abs_mini or v==-abs_mini)
771
772 # ------------------------------------------------------------------------------
773    def __iter__(self):
774       """Itère sur les éléments de la colonne"""
775       for row in self.Table:
776          # si l'élément n'est pas présent on retourne None
777          yield row.get(self.para)
778          #yield row[self.para]
779
780 # ------------------------------------------------------------------------------
781    def __getitem__(self, i):
782       """Retourne la ième valeur d'une colonne"""
783       return self.values()[i]
784
785 # ------------------------------------------------------------------------------
786    def values(self):
787       """Renvoie la liste des valeurs"""
788       return [r.get(self.para,None) for r in self.Table]
789
790    def not_none_values(self):
791       """Renvoie la liste des valeurs non 'None'"""
792       return [val for val in self.values() if val != None]
793
794 # ------------------------------------------------------------------------------
795    # équivalences avec les opérateurs dans Aster
796    LE=__le__
797    LT=__lt__
798    GE=__ge__
799    GT=__gt__
800    EQ=__eq__
801    NE=__ne__
802    def VIDE(self):
803       return self.__eq__(None)
804    def NON_VIDE(self):
805       return self.__ne__(None)
806
807 # ------------------------------------------------------------------------------
808 # ------------------------------------------------------------------------------
809 # ------------------------------------------------------------------------------
810 def sort_table(rows, l_para, w_para, reverse=False):
811    """Sort list of dict.
812       rows     : list of dict
813       l_para   : list of the keys of dict
814       w_para   : keys of the sort
815    """
816    c_para=[i for i in l_para if i not in w_para]
817    new_rows=rows
818    # rename sort keys by "__" + number + para
819    # ("__" to avoid conflict with existing parameters)
820    for i in w_para :
821       new_key= '__'+str(w_para.index(i))+i
822       for row in new_rows :
823          row[new_key]=row[i]
824          del row[i]
825    # rename others parameters by "___" + para
826    # ("___" to be after sort keys)
827    for i in c_para :
828       new_key= '___'+i
829       for row in new_rows :
830          row[new_key]=row[i]
831          del row[i]
832    # sort
833    new_rows.sort()
834    # reversed sort
835    if reverse:
836       new_rows.reverse()
837    for i in w_para :
838       old_key= '__'+str(w_para.index(i))+i
839       for row in new_rows :
840          row[i]=row[old_key]
841          del row[old_key]
842    for i in c_para :
843       old_key= '___'+i
844       for row in new_rows :
845          row[i]=row[old_key]
846          del row[old_key]
847    return new_rows
848
849 # ------------------------------------------------------------------------------
850 def FMT(dform, nform, typAster=None, larg=0, val=''):
851    """Retourne un format d'impression Python à partir d'un type Aster ('R','I',
852    'K8', 'K16'...). Si typAster==None, retourne dform[nform].
853       larg : largeur minimale du format (val permet de ne pas ajouter des blancs
854       si la chaine à afficher est plus longue que le format, on prend le partie
855       de ne pas tronquer les chaines)
856    """
857    if typAster==None:
858       fmt=dform[nform]
859    elif typAster in ('I', 'R'):
860       if nform=='formK':
861          # convertit %12.5E en %-12s
862          fmt=re.sub('([0-9]+)[\.0-9]*[diueEfFgG]+','-\g<1>s',dform['form'+typAster])
863       else:
864          fmt=dform[nform]
865    else:
866       # typAster = Kn
867       fmt='%-'+typAster[1:]+'s'
868    # on ajoute éventuellement des blancs pour atteindre la largeur demandée
869    if larg<>0:
870       fmt=' '*max(min(larg-len(val),larg-len(fmt % 0)),0) + fmt
871    return fmt
872
873 # ------------------------------------------------------------------------------
874 def merge(tab1, tab2, labels=[]):
875    """Assemble les deux tables tb1 et tb2 selon une liste de labels communs.
876       Si labels est vide:
877        - les lignes de tb2 sont ajoutés à celles de tb1,
878       sinon :
879        - si on trouve les valeurs de tb2 sur les labels dans tb1 (et une seule fois),
880          on surcharge tb1 avec les lignes de tb2 ;
881        - sinon on ajoute la ligne de tb2 à la fin de tb1.
882    """
883    tb1 = tab1.copy()
884    tb2 = tab2.copy()
885    if type(labels) not in EnumTypes:
886       labels=(labels,)
887    for key in labels :
888        if key not in tb1.para : UTMESS('F','Table','Erreur, label non présent %s' % key)
889        if key not in tb2.para : UTMESS('F','Table','Erreur, label non présent %s' % key)
890    # ensemble des paramètres et des types
891    n_para=tb1.para[:]
892    n_type=tb1.type[:]
893    for i in tb2.para:
894       if i not in tb1.para:
895          n_para.append(i)
896          n_type.append(tb2.type[tb2.para.index(i)])
897    # restriction des lignes aux labels communs (peu cher en cpu)
898    rows1 = tb1.rows
899    dlab1 = {}
900    for i1 in range(len(rows1)):
901       tu1 = tuple(map(rows1[i1].__getitem__, labels))
902       if dlab1.get(tu1, '') == '':
903          dlab1[tu1] = i1
904       else:
905          dlab1[tu1] = None
906    # restriction des lignes aux labels communs (peu cher en cpu)
907    rows2 = tb2.rows
908    dlab2 = {}
909    for i2 in range(len(rows2)):
910       tu2 = tuple(map(rows2[i2].__getitem__, labels))
911       if dlab2.get(tu2, '') == '':
912          dlab2[tu2] = i2
913       else:
914          dlab2[tu2] = None
915    # creation de dic1 : dictionnaire de correspondance entre les 
916    # lignes a merger dans les deux tableaux
917    dic1 = {}
918    for cle in dlab1.keys():
919       if dlab1[cle] == None or cle == ():
920          bid = dlab1.pop(cle)
921    for cle in dlab2.keys():
922       if dlab2[cle] == None or cle == ():
923          bid = dlab2.pop(cle)
924    for cle in dlab2.keys():
925       if dlab1.has_key(cle):
926          dic1[dlab2[cle]] = dlab1[cle]
927    # insertion des valeurs de tb2 dans tb1 quand les labels sont communs
928    # (et uniques dans chaque table) OU ajout de la ligne de tb2 dans tb1
929    i2 = -1
930    for r2 in rows2:
931       i2 += 1
932       try:
933          rows1[dic1[i2]].update(r2)
934       except KeyError:
935          rows1.append(r2)
936    # concaténation des titres + info sur le merge
937    tit = '\n'.join([tb1.titr, tb2.titr, 'MERGE avec labels=%s' % repr(labels)])
938    return Table(rows1, n_para, n_type, tit)
939
940 # ------------------------------------------------------------------------------
941 def _typaster(obj, prev=None, strict=False):
942    """Retourne le type Aster ('R', 'I', Kdef) correspondant à l'objet obj.
943    Si prev est fourni, on vérifie que obj est du type prev.
944    Si strict=False, on autorise que obj ne soit pas du type prev s'ils sont
945    tous les deux numériques ; dans ce cas, on retourne le "type enveloppe" 'R'.
946    """
947    dtyp={
948       IntType    : 'I',
949       FloatType  : 'R',
950       StringType : Kdef, UnicodeType : Kdef,
951       NoneType   : 'I',
952    }
953    if type(obj) in dtyp.keys():
954       typobj=dtyp[type(obj)]
955       if prev in [None, typobj]:
956          return typobj
957       elif strict:   # prev<>None et typobj<>prev et strict
958          raise TypeError, "La valeur %s n'est pas de type %s" % (repr(obj),repr(prev))
959       elif prev in ('I','R') and typobj in ('I','R'):
960          return 'R'
961       else:
962          raise TypeError, "La valeur %s n'est pas compatible avec le type %s" \
963                % (repr(obj),repr(prev))
964    else:
965       raise TypeError, 'Une table ne peut contenir que des entiers, réels ' \
966                        'ou chaines de caractères.'
967                   
968 # ------------------------------------------------------------------------------
969 # fonctions utilitaires
970 def _func_test_abs(v, VALE, PRECISION):
971    """Retourne True si v est parmi VALE à PRECISION près en absolu
972    """
973    for x in VALE:
974       if v != None and (x-PRECISION <= v <= x+PRECISION):
975          return True
976    return False
977
978 def _func_test_rela(v, VALE, PRECISION):
979    """Retourne True si v est parmi VALE à PRECISION près en relatif
980    """
981    for x in VALE: 
982       if v != None and (x*(1.-PRECISION) <= v <= x*(1.+PRECISION)):
983          return True
984    return False