1 #@ MODIF Table Utilitai DATE 17/05/2005 AUTEUR DURAND C.DURAND
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.
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.
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 # ======================================================================
21 # RESPONSABLE MCOURTOI M.COURTOIS
28 EnumTypes=(ListType, TupleType)
29 NumberTypes=(IntType, LongType, FloatType, ComplexType)
31 # try/except pour utiliser hors aster
33 from Utilitai.Utmess import UTMESS
35 def UTMESS(code,sprg,texte):
36 fmt='\n <%s> <%s> %s\n\n'
37 print fmt % (code,sprg,texte)
39 if not sys.modules.has_key('Graph'):
41 from Utilitai import Graph
45 # formats de base (identiques à ceux du module Graph)
47 'csep' : ' ', # séparateur
48 'ccom' : '#', # commentaire
49 'cdeb' : '', # début de ligne
50 'cfin' : '\n', # fin de ligne
51 'formK' : '%-8s', # chaines
52 'formR' : '%12.5E', # réels
53 'formI' : '%8d' # entiers
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).
65 return self.ReprTable()
66 def Croise(self,**kargs):
67 raise StandardError, 'Must be defined in a derived class'
69 # ------------------------------------------------------------------------------
70 def Impr(self,FICHIER=None,FORMAT='TABLEAU',dform=None,**opts):
71 """Impresssion de la Table selon le format spécifié.
72 FICHIER : nom du(des) fichier(s). Si None, on dirige vers stdout
73 dform : dictionnaire de formats d'impression (format des réels,
74 commentaires, saut de ligne...)
78 'TABLEAU' : { 'mode' : 'a', 'driver' : self.ImprTableau, },
79 'ASTER' : { 'mode' : 'a', 'driver' : self.ImprTableau, },
80 'XMGRACE' : { 'mode' : 'a', 'driver' : self.ImprGraph, },
81 'AGRAF' : { 'mode' : 'a', 'driver' : self.ImprTableau, },
82 'TABLEAU_CROISE' : { 'mode' : 'a', 'driver' : self.ImprTabCroise, },
87 'dform' : DicForm.copy(),
88 'mode' : para[FORMAT]['mode'],
90 if dform<>None and type(dform)==DictType:
91 kargs['dform'].update(dform)
95 if not kargs.get('PAGINATION'):
96 # call the associated driver
97 para[FORMAT]['driver'](**kargs)
100 if not type(kargs['PAGINATION']) in EnumTypes:
101 ppag=[kargs['PAGINATION'],]
103 ppag=list(kargs['PAGINATION'])
104 del kargs['PAGINATION']
106 # paramètres hors ceux de la pagination
107 lkeep=[p for p in self.para if ppag.count(p)==0]
108 # création des listes des valeurs distinctes
111 lvp=getattr(self,p).values()
114 if it<>None and lvn.count(it)==0:
118 # création des n-uplets
119 s = '[['+','.join(['x'+str(i) for i in range(npag)])+'] '
120 s+= ' '.join(['for x'+str(i)+' in lvd['+str(i)+']' for i in range(npag)])+']'
123 except SyntaxError, s:
124 UTMESS('F','Table','Erreur lors de la construction des n-uplets')
125 # pour chaque n-uplet, on imprime la sous-table
128 for i in range(npag):
129 tab = tab & (getattr(tab,ppag[i]) == nup[i])
132 tab.titr += sl+ppag[i]+': '+str(nup[i])
133 tab[lkeep].Impr(**kargs)
135 # ------------------------------------------------------------------------------
136 def ImprTableau(self,**kargs):
137 """Impression au format TABLEAU ou ASTER
140 if kargs.get('FICHIER')<>None:
141 f=open(kargs['FICHIER'],kargs['mode'])
145 f.write(self.ReprTable(**kargs) + '\n')
147 if kargs.get('FICHIER')<>None:
150 def ReprTable(self,FORMAT='TABLEAU',dform=DicForm,**ignore):
151 """Représentation d'une Table ou d'une Colonne sous forme d'un tableau.
156 if not type(para) in EnumTypes:
159 # est-ce que l'attribut .type est renseigné ?
160 typdef=typ<>[None]*len(typ)
162 # ['']+ pour ajouter un séparateur en début de ligne
164 # lmax : largeur max des colonnes = max(form{K,R,I},len(parametre))
168 larg_max=max([len(str(p))] + \
169 [len(FMT(dform,k,t) % 0) for k in ('formK','formR','formI')])
170 lspa.append(FMT(dform,'formK',t,larg_max,str(p)) % p)
171 lmax.append(larg_max)
173 stype=dform['csep'].join([''] + \
174 [FMT(dform,'formK',typ[i],lmax[i]) % typ[i] for i in range(len(para))])
178 ASTER=(FORMAT=='ASTER')
180 txt.append('#DEBUT_TABLE')
183 txt.extend(['#TITRE '+lig for lig in self.titr.split('\n')])
185 txt.append(self.titr)
186 txt.append(dform['csep'].join(lspa))
196 if type(rep) is FloatType:
197 lig.append(FMT(dform,'formR',t,lmax[i]) % rep)
199 elif type(rep) is IntType:
200 lig.append(FMT(dform,'formI',t,lmax[i]) % rep)
207 s=FMT(dform,'formK',t,lmax[i],rep) % str(rep)
208 # format AGRAF = TABLEAU + '\' devant les chaines de caractères !
213 txt.append(dform['csep'].join(lig))
215 txt.append('#FIN_TABLE')
216 return dform['cfin'].join(txt)
217 # ------------------------------------------------------------------------------
218 def ImprTabCroise(self,**kargs):
219 """Impression au format TABLEAU_CROISE d'une table ayant 3 paramètres.
221 # création du tableau croisé et impression au format TABLEAU
223 kargs['FORMAT']='TABLEAU'
225 # ------------------------------------------------------------------------------
226 def ImprGraph(self,**kargs):
227 """Impression au format XMGRACE : via le module Graph
230 if len(self.para)<>2:
231 UTMESS('A','Table','La table doit avoir exactement deux paramètres.')
233 lx, ly = [[v for v in getattr(self,p).values() if v<>None] for p in self.para]
240 if args['LEGENDE']==None: del args['LEGENDE']
241 Graph.AjoutParaCourbe(dicC, args)
242 graph.AjoutCourbe(**dicC)
244 # Surcharge des propriétés du graphique et des axes
245 # (bloc quasiment identique dans impr_fonction_ops)
246 if args.get('TITRE'): graph.Titre=args['TITRE']
247 if args.get('BORNE_X'):
248 graph.Min_X=args['BORNE_X'][0]
249 graph.Max_X=args['BORNE_X'][1]
250 if args.get('BORNE_Y'):
251 graph.Min_Y=args['BORNE_Y'][0]
252 graph.Max_Y=args['BORNE_Y'][1]
253 if args.get('LEGENDE_X'): graph.Legende_X=args['LEGENDE_X']
254 if args.get('LEGENDE_Y'): graph.Legende_Y=args['LEGENDE_Y']
255 if args.get('ECHELLE_X'): graph.Echelle_X=args['ECHELLE_X']
256 if args.get('ECHELLE_Y'): graph.Echelle_Y=args['ECHELLE_Y']
257 if args.get('GRILLE_X'): graph.Grille_X=args['GRILLE_X']
258 if args.get('GRILLE_Y'): graph.Grille_Y=args['GRILLE_Y']
263 UTMESS('A','Table','Les cellules ne doivent contenir que des nombres réels')
265 # ------------------------------------------------------------------------------
266 # ------------------------------------------------------------------------------
267 # ------------------------------------------------------------------------------
268 class Table(TableBase):
269 """Une table est construite comme une liste de lignes, chaque ligne est
271 On crée puis on ajoute les lignes avec la méthode append :
273 t.append(dict(a=1,b=2))
274 t.append(dict(a=3,b=4))
275 La méthode __iter__ définit un itérateur sur les lignes de la table,
276 __repr__ retourne une représentation de la table, utilisée par "print t".
277 Grace à la classe Colonne et à sa méthode _extract, il est possible
278 de construire une sous-table qui satisfait un critère donné.
279 Le critère est donné par une fonction Python qui retourne vrai
280 ou faux si la valeur d'une colonne respecte le critère ou non.
284 soustable = t.a._extract(critere)
285 t.a retourne un objet intermédiaire de la classe Colonne qui mémorise
286 le nom de la colonne demandée (a, ici).
288 def __init__(self, rows=[], para=[], typ=[], titr=''):
289 """Constructeur de la Table :
290 rows : liste des lignes (dict)
291 para : liste des paramètres
292 type : liste des types des paramètres
293 titr : titre de la table
295 self.rows=[r for r in rows if r.values()<>[None]*len(r.values())]
297 if len(typ)==len(self.para):
300 self.type=[None]*len(self.para)
303 def append(self, obj):
304 """Ajoute une ligne (type dict) à la Table"""
305 self.rows.append(obj)
308 """Itère sur les lignes de la Table"""
309 return iter(self.rows)
311 def __getattr__(self, column):
312 """Construit un objet intermediaire (couple table, colonne)"""
314 if not column in self.para:
317 typ=self.type[self.para.index(column)]
318 return Colonne(self, column, typ)
320 def sort(self, CLES=None, ORDRE='CROISSANT'):
322 CLES : liste des clés de tri
323 ORDRE : CROISSANT ou DECROISSANT (de longueur 1 ou len(keys))
325 # par défaut, on prend tous les paramètres
328 if not type(CLES) in EnumTypes:
332 self.rows=sort_table(self.rows, self.para, CLES, (ORDRE=='DECROISSANT'))
333 # if not type(order) in EnumTypes:
335 # print 'TRI clés=%s, order=%s' % (keys,order)
336 # # on ne garde que le premier si les longueurs sont différentes
337 # if len(order)<>len(keys):
340 # # si toutes les valeurs sont identiques, on peut ne garder que la 1ère
342 # for o in order: d[o]=None
343 # if len(order)<>len(keys) or len(d.keys())==1:
346 # self.rows=sort_table(self.rows, self.para, keys, (order[0]=='DECROISSANT'))
348 # # de la dernière clé à la première
349 # for k,o in [(keys[i],order[i]) for i in range(len(keys)-1,-1,-1)]:
350 # print 'TRI : clé=%s, order=%s' % (k,o)
351 # self.rows=sort_table(self.rows, self.para, [k], (o=='DECROISSANT'))
353 def __delitem__(self, args):
354 """Supprime les colonnes correspondantes aux éléments de args """
355 if not type(args) in EnumTypes:
361 del new_type[new_para.index(item)]
362 new_para.remove(item)
363 for line in new_rows : del line[item]
364 return Table(new_rows, new_para, new_type, self.titr)
366 def __getitem__(self, args):
367 """Extrait la sous table composée des colonnes dont les paramètres sont dans args """
368 if not type(args) in EnumTypes:
372 #print '<getitem> args=',args
376 for item in new_para:
377 if not item in self.para:
379 new_type.append(self.type[self.para.index(item)])
382 for item in new_para:
383 new_line[item]=line.get(item)
384 new_rows.append(new_line)
385 return Table(new_rows, new_para, new_type, self.titr)
387 def __and__(self, other):
388 """Intersection de deux tables (opérateur &)"""
389 if other.para<>self.para:
390 UTMESS('A','Table','Les paramètres sont différents')
393 tmp = [ r for r in self if r in other.rows ]
394 return Table(tmp, self.para, self.type, self.titr)
396 def __or__(self, other):
397 """Union de deux tables (opérateur |)"""
398 if other.para<>self.para:
399 UTMESS('A','Table','Les paramètres sont différents')
403 tmp.extend([ r for r in other if r not in self ])
404 return Table(tmp, self.para, self.type[:], self.titr)
407 """Renvoie la table sous la forme d'un dictionnaire de listes dont les
408 clés sont les paramètres.
411 for column in self.para:
412 dico[column]=Colonne(self, column).values()
415 def Array(self,Para,Champ):
416 """Renvoie sous forme de NumArray le résultat d'une extraction dans une table
417 méthode utile à macr_recal
420 __Rep = self[Para,Champ].values()
421 F=Numeric.zeros((len(__Rep[Para]),2),Numeric.Float)
422 for i in range(len(__Rep[Para])):
423 F[i][0] = __Rep[Para][i]
424 F[i][1] = __Rep[Champ][i]
429 """Retourne un tableau croisé P3(P1,P2) à partir d'une table ayant
430 trois paramètres (P1, P2, P3).
432 if len(self.para)<>3:
433 UTMESS('A','Table','La table doit avoir exactement trois paramètres.')
435 py, px, pz = self.para
436 ly, lx, lz = [getattr(self,p).values() for p in self.para]
438 #lpz='%s=f(%s,%s)' % (pz,px,py)
439 lpz='%s/%s' % (px,py)
441 # attention aux doublons dans lx et ly
443 if it<>None and new_para.count(it)==0:
447 if it<>None and newx.count(it)==0:
452 taux = (getattr(self,px)==x)
456 new_type=[self.type[0],] + [self.type[2]]*len(ly)
458 if new_titr<>'': new_titr+='\n'
459 new_titr+=pz + ' FONCTION DE ' + px + ' ET ' + py
460 return Table(new_rows, new_para, new_type, new_titr)
462 # ------------------------------------------------------------------------------
463 # ------------------------------------------------------------------------------
464 # ------------------------------------------------------------------------------
465 class Colonne(TableBase):
466 """Classe intermédiaire pour mémoriser un couple (table, nom de colonne)
467 et exprimer les critères d'extraction sous une forme naturelle en python
468 en surchargeant les operateurs <, >, <> et =.
469 Alors on peut écrire la requete simple :
471 Ainsi que des requetes plus complexes :
472 soustable=t.a<10 & t.b <4
474 soustable=t.a<10 | t.b <4
475 Les "alias" EQ, NE, LE, LT, GE, GT permettent à la macro IMPR_TABLE
476 d'utiliser directement le mot-clé utilisateur CRIT_COMP défini dans le
477 catalogue : getattr(Table,CRIT_COMP).
479 def __init__(self, table, column, typ=None):
480 """Constructeur (objet Table associé, paramètre de la colonne, type du
484 self.rows=self.Table.rows
489 def _extract(self, fun):
490 """Construit une table avec les lignes de self.Table
491 dont l'élément de nom self.para satisfait le critère fun,
492 fun est une fonction qui retourne vrai ou faux
494 return Table([row for row in self.Table if fun(row.get(self.para))], self.Table.para, self.Table.type, self.Table.titr)
496 def __le__(self, VALE):
497 return self._extract(lambda v: v<>None and v<=VALE)
499 def __lt__(self, VALE):
500 return self._extract(lambda v: v<>None and v<VALE)
502 def __ge__(self, VALE):
503 return self._extract(lambda v: v<>None and v>=VALE)
505 def __gt__(self, VALE):
506 return self._extract(lambda v: v<>None and v>VALE)
508 def __eq__(self, VALE, CRITERE='RELATIF', PRECISION=0.):
509 if type(VALE) in EnumTypes :
510 return self._extract(lambda v: v in VALE)
511 if PRECISION==0. or not type(VALE) in NumberTypes:
512 if type(VALE) in StringTypes:
513 return self._extract(lambda v: v<>None and str(v).strip()==VALE.strip())
515 return self._extract(lambda v: v==VALE)
517 if CRITERE=='ABSOLU':
521 vmin=(1.-PRECISION)*VALE
522 vmax=(1.+PRECISION)*VALE
523 return self._extract(lambda v: v<>None and vmin<v<vmax)
525 def __ne__(self, VALE, CRITERE='RELATIF', PRECISION=0.):
526 if type(VALE) in EnumTypes :
527 return self._extract(lambda v: v not in VALE)
528 if PRECISION==0. or not type(VALE) in NumberTypes:
529 if type(VALE) in StringTypes:
530 return self._extract(lambda v: v<>None and str(v).strip()<>VALE.strip())
532 return self._extract(lambda v: v<>VALE)
534 if CRITERE=='ABSOLU':
538 vmin=(1.-PRECISION)*VALE
539 vmax=(1.+PRECISION)*VALE
540 return self._extract(lambda v: v<>None and (v<vmin or vmax<v))
543 # important pour les performances de récupérer le max une fois pour toutes
545 return self._extract(lambda v: v==maxi)
548 # important pour les performances de récupérer le min une fois pour toutes
550 return self._extract(lambda v: v==mini)
553 # important pour les performances de récupérer le max une fois pour toutes
554 abs_maxi=max([abs(v) for v in self.values() if type(v) in NumberTypes])
555 return self._extract(lambda v: v==abs_maxi or v==-abs_maxi)
558 # important pour les performances de récupérer le min une fois pour toutes
559 abs_mini=min([abs(v) for v in self.values() if type(v) in NumberTypes])
560 # tester le type de v est trop long donc pas de abs(v)
561 return self._extract(lambda v: v==abs_mini or v==-abs_mini)
564 """Itère sur les éléments de la colonne"""
565 for row in self.Table:
566 # si l'élément n'est pas présent on retourne None
567 yield row.get(self.para)
568 #yield row[self.para]
570 def __getitem__(self, i):
571 """Retourne la ième valeur d'une colonne"""
572 return self.values()[i]
575 """Renvoie la liste des valeurs"""
576 return [r[self.para] for r in self.Table]
578 # équivalences avec les opérateurs dans Aster
585 def VIDE(self) : return self.__eq__(None)
586 def NON_VIDE(self): return self.__ne__(None)
588 # ------------------------------------------------------------------------------
589 # ------------------------------------------------------------------------------
590 # ------------------------------------------------------------------------------
591 def sort_table(rows,l_para,w_para,reverse=False):
592 """Sort list of dict.
594 l_para : list of the keys of dict
595 w_para : keys of the sort
597 c_para=[i for i in l_para if i not in w_para]
600 new_key= '__'+str(w_para.index(i))+i
601 for row in new_rows :
606 for row in new_rows :
613 old_key= '__'+str(w_para.index(i))+i
614 for row in new_rows :
619 for row in new_rows :
624 # ------------------------------------------------------------------------------
625 def FMT(dform, nform, typAster=None, larg=0, val=''):
626 """Retourne un format d'impression Python à partir d'un type Aster ('R','I',
627 'K8', 'K16'...). Si typAster==None, retourne dform[nform].
628 larg : largeur minimale du format (val permet de ne pas ajouter des blancs
629 si la chaine à afficher est plus longue que le format, on prend le partie
630 de ne pas tronquer les chaines)
634 elif typAster in ('I', 'R'):
636 # convertit %12.5E en %-12s
637 fmt=re.sub('([0-9]+)[\.0-9]*[diueEfFgG]+','-\g<1>s',dform['form'+typAster])
638 #print nform, typAster, fmt
643 fmt='%-'+typAster[1:]+'s'
644 # on ajoute éventuellement des blancs pour atteindre la largeur demandée
646 fmt=' '*max(min(larg-len(val),larg-len(fmt % 0)),0) + fmt
649 # ------------------------------------------------------------------------------
650 # ------------------------------------------------------------------------------
651 # ------------------------------------------------------------------------------
652 if __name__ == "__main__":
654 {'NOEUD': 'N1' ,'NUME_ORDRE': 1 ,'INST': 0.5, 'DX': -0.00233, 'COOR_Y': 0.53033,},
655 {'NOEUD': 'N1' ,'NUME_ORDRE': 2 ,'INST': 1.0, 'DX': -0.00467, 'COOR_Y': 0.53033,},
656 {'NOEUD': 'N1' ,'NUME_ORDRE': 3 ,'INST': 1.5, 'DX': -0.00701, 'COOR_Y': 0.53033,},
657 {'NOEUD': 'N1' ,'NUME_ORDRE': 4 ,'INST': 2.0, 'DX': -0.00934, 'COOR_Y': 0.53033,},
658 {'NOEUD': 'N1' ,'NUME_ORDRE': 5 ,'INST': 2.5, 'DX': -0.01168, 'COOR_Y': 0.53033,},
659 {'NOEUD': 'N2' ,'NUME_ORDRE': 11,'INST': 5.5, 'DX': -0.00233, 'COOR_Y': 0.53033,},
660 {'NOEUD': 'N2' ,'NUME_ORDRE': 12,'INST': 6.0, 'DX': -0.00467, 'COOR_Y': 0.53033,},
661 {'NOEUD': 'N2' ,'NUME_ORDRE': 13,'INST': 6.5, 'DX': -0.00701, 'COOR_Y': 0.53033,},
662 {'NOEUD': 'N2' ,'NUME_ORDRE': 14,'INST': 7.0, 'DX': -0.00934, 'COOR_Y': 0.53033,},
663 {'NOEUD': 'N2' ,'NUME_ORDRE': 15,'INST': 7.5, 'DX': -0.01168, 'COOR_Y': 0.53033,},
666 random.shuffle(listdic)
667 listpara=['NOEUD','NUME_ORDRE','INST','COOR_Y','DX']
668 listtype=['K8','I','R','R','R']
669 t=Table(listdic,listpara,listtype)
676 print "------Table initiale----"
679 print "--------- CRIT --------"
680 print t.NUME_ORDRE <=5
682 print "------- CRIT & CRIT -----"
683 print (t.NUME_ORDRE < 10) & (t.INST >=1.5)
685 print "----- EQ maxi / min(col), max(col) ------"
686 print t.DX == max(t.DX)
689 print "------ getitem sur 2 paramètres ------"
692 print t['DX','NUME_ORDRE']
693 print "------ sort sur INST ------"
697 print "------- TABLEAU_CROISE ------"
698 tabc=t['NOEUD','INST','DX']
699 tabc.Impr(FORMAT='TABLEAU_CROISE')
704 ldic.append({'IND':float(i), 'VAL' : random.random()*i})
706 t3=Table(ldic, para, titr='Table aléatoire')
707 col=t3.VAL.ABS_MAXI()
712 tg=tabc['INST','DX'].DX.NON_VIDE()
713 #tg.Impr(FORMAT='XMGRACE')
716 g.Titre="Tracé d'une fonction au format TABLEAU"
717 g.AjoutCourbe(Val=[tg.INST.values(), tg.DX.values()], Lab=['INST','DX'])
718 g.Trace(FORMAT='TABLEAU')
720 # t.Impr(PAGINATION='NOEUD')
721 t.Impr(PAGINATION=('NOEUD','INST'))