Categorie.py 36.2 KB
Newer Older
1
2
3
#!/usr/bin/env python
# -*- coding: utf-8 -*-

4
from Morphisme import Morphisme, Composition
5
from config import *
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
6

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
7
8
if GRAPHVIZ_ENABLED:
    from graphviz import Digraph
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
9
from collections import defaultdict
10
import itertools
11
import Diagramme
12
import copy
13

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
14

15
class Categorie:
16
17
18
19
    """Catégorie libre par défaut, on peut spécifier les contraintes
    de composition en ajoutant des diagrammes commutatifs.
    Les compositions de morphismes ne sont pas ajoutées par défaut,
    elles existent en potentialité."""
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
20
21
22
23

    nb_viz = 0  # nombre de graphes graphviz générés

    def __init__(self, nom="CategorieFinie"):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
24
        self.nom = nom
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
25
26
27
28
29
30
31
32
        self.objets = []  # les objets doivent être hashable
        self.morphismes = []  # liste des morphismes de base (pas les composes)
        self.identites = dict()  # à chaque objet on associe son morphisme identité, ce dernier doit aussi être
        # présent dans morphismes
        self.morph_sortants = defaultdict(list)  # Liste des morphismes sortant non triviaux d'un objet
        self.morph_entrants = defaultdict(list)
        self.diagrammes = []  # liste des diagrammes commutatifs

33
34
35
        class Composee(Composition):
            """La loi de composition est inclus dans la construction de la composee : 
            en construisant la composee de deux morphismes identifiés, le résultat sera le même."""
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
36
37
38
39
40
            loi_de_composition = {}  # dico Composition:Composition

            # /!\ La loi de composition doit être un graphe acyclique simplificateur /!\

            def verifier_coherence(noeuds_visites=tuple()):
41
42
                for noeud in Composee.loi_de_composition:
                    if noeud not in noeuds_visites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
43
44
                        if not noeud.is_identite and not Composee.loi_de_composition[noeud].is_identite and len(
                                noeud.composee) == 1 and len(Composee.loi_de_composition[noeud]) == 1:
45
                            raise Exception("Loi de composition associe deux fleches elementaires")
46
                        if len(noeud) < len(Composee.loi_de_composition[noeud]):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
47
48
49
                            raise Exception(
                                "Loi de composition pas simplificatrice (image d'une composition est une composition de taille superieure) : " +
                                str(Composition(*noeud)) + " -> " + str(Composee.loi_de_composition[noeud]))
50
                        noeuds_visites += tuple([noeud])
51
                        if Composee.loi_de_composition[noeud] in noeuds_visites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
52
53
54
                            raise Exception(
                                "Loi de composition n'est pas acyclique : " + str(Composition(*noeud)) + " -> " + str(
                                    Composee.loi_de_composition[noeud]))
55
                        Composee.verifier_coherence(noeuds_visites)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
56

57
            def _simplifier_composee(morphismes):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
58
59
60
61
                morphismes2 = [m for compo in morphismes for m in compo]
                for i in range(len(morphismes2), 1, -1):
                    for j in range(0, len(morphismes2) - i + 1):
                        sous_composee = Composition(*morphismes2[j:j + i])
62
                        if sous_composee in self.Composee.loi_de_composition:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
63
64
65
66
                            morphismes2 = morphismes2[0:j] + [
                                self.Composee.loi_de_composition[sous_composee]] + morphismes2[j + i:]
                            return Composee._simplifier_composee(morphismes2)
                return morphismes2
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
67
68

            def __new__(cls, *morphismes):
69
70
                morphismes = Composee._simplifier_composee(morphismes)
                composition = Composition(*morphismes)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
71
72
                if type(composition) is Composition:  # si la composition n'est pas simplifiée en morphisme
                    instance = super(Composee, cls).__new__(cls, *morphismes)
73
                    return instance
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
74
                else:  # si la composition est simplifiée en morphisme unitaire
75
                    return composition
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
76
77

            def __init__(self, *morphismes):
78
                morphismes = Composee._simplifier_composee(morphismes)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
79
                Composition.__init__(self, *morphismes)
80
81

        self.Composee = Composee
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
82

83
    def __copy__(self):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
84
        newone = Categorie(self.nom)
85
86
87
        newone.objets = copy.copy(self.objets)
        newone.morphismes = copy.copy(self.morphismes)
        newone.identites = copy.copy(self.identites)
88
89
90
91
92
93
        newone.morph_sortants = defaultdict(list)
        newone.morph_entrants = defaultdict(list)
        for cle in self.morph_sortants:
            newone.morph_sortants[cle] = copy.copy(self.morph_sortants[cle])
        for cle in self.morph_entrants:
            newone.morph_entrants[cle] = copy.copy(self.morph_entrants[cle])
94
        newone.diagrammes = copy.copy(self.diagrammes)
95
        newone.Composee.loi_de_composition = copy.copy(self.Composee.loi_de_composition)
96
        return newone
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
97
98
99
100
101
102
103

    def __eq__(self, other):
        return self.objets == other.objets and self.morphismes == other.morphismes and \
               self.identites == other.identites and self.morph_sortants == other.morph_sortants \
               and self.morph_entrants == other.morph_entrants and self.diagrammes == other.diagrammes \
               and self.Composee.loi_de_composition == other.Composee.loi_de_composition

104
105
    def __hash__(self):
        return super().__hash__()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
106
107
108
        
    def __str__(self):
        return str(self.nom)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
109

110
    def fleches_elem(self, source, cible, inclure_id = True):
Guillaume Sabbagh's avatar
Limite    
Guillaume Sabbagh committed
111
        """Renvoie la liste de flèches élémentaires de source à cible."""
112
        return [morph for morph in self.morph_sortants[source] if morph.cible == cible and (inclure_id or not morph.is_identite)]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
113

114
115
116
117
118
    def verifier_coherence(self):
        """Vérifie la cohérence de la structure (tous les axiomes des catégories sont vérifiés)."""
        m = self.morphismes
        if len(self.objets) == 0:
            ## S'il n'y a aucun objet, on vérifie que tout le reste est vide aussi
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
119
120
            assert (len(m) == len(self.morph_sortants) == len(self.morph_entrants) == len(self.diagrammes) == len(
                self.Composee.loi_de_composition) == 0)
121
122
123
        else:
            ## On vérifie l'existence de l'identité pour tous les objets (O(n))
            for o in self.objets:
124
                if o not in self.identites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
125
126
                    raise Exception("Pas de morphisme identite pour l'objet " + str(o))

127
128
            ## L'associativité est vérifiée par construction de la composee
            ## On vérifie que les morphismes identifiés ont les mêmes sources et cibles
129
130
            for morph in self.Composee.loi_de_composition:
                if morph.source != self.Composee.loi_de_composition[morph].source:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
131
132
133
                    raise Exception(
                        "Morphismes identifies avec des sources differentes : " + ''.join(map(str, m)) + "," + str(
                            self.Composee.loi_de_composition[m]))
134
                if morph.cible != self.Composee.loi_de_composition[morph].cible:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
135
136
137
138
                    raise Exception(
                        "Morphismes identifies avec des cibles differentes : " + ''.join(map(str, m)) + "," + str(
                            self.Composee.loi_de_composition[m]))

139
140
141
142
            ## L'axiome d'identité est vérifié par construction de la composee
            ## On vérifie qu'il n'y a pas trop d'identités
            ident = [morph for morph in m if morph.is_identite]
            if len(ident) > len(self.objets):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
143
                raise Exception("Trop d'identites : " + ','.join(map(str, ident)))
144

145
146
147
148
149
150
            ## On vérifie que la liste morph_sortants est correcte
            for obj in self.objets:
                for morph in m:
                    if not morph.is_identite:
                        if morph.source == obj:
                            if morph not in self.morph_sortants[obj]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
151
152
                                # print(' , '.join(map(str,self.morphismes)))
                                print(' , '.join(map(str, m)))
153
                                print(morph)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
154
                                raise Exception("Erreur morph_sortants : morphisme manquant " + str(morph))
155
156
                        if morph.source != obj:
                            if morph in self.morph_sortants[obj]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
157
158
                                raise Exception("Erreur morph_sortants : morphisme en trop " + str(morph))

159
160
161
162
163
164
            ## On vérifie que la liste morph_entrants est correcte
            for obj in self.objets:
                for morph in m:
                    if not morph.is_identite:
                        if morph.cible == obj:
                            if morph not in self.morph_entrants[obj]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
165
                                raise Exception("Erreur morph_entrants : morphisme manquant " + str(morph))
166
167
                        if morph.cible != obj:
                            if morph in self.morph_entrants[obj]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
168
169
170
171
                                raise Exception("Erreur morph_entrants : morphisme en trop " + str(morph))

            self.Composee.verifier_coherence()

172
    def ajouter_objet(self, objet):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
173
        if objet in self.objets:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
174
175
            raise Exception("Objet deja present dans la categorie : tentative d'ajout de " + str(objet) + " echoue")

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
176
        ##on ajoute l'objet et l'identité associée
177
        self.objets += [objet]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
178
        self.identites[objet] = Morphisme(objet, objet, "Id" + str(objet), True)
179
        self.morphismes += [self.identites[objet]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
180

181
182
        if TOUJOURS_VERIFIER_COHERENCE:
            self.verifier_coherence()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
183

184
    def ajouter_objets(self, objets):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
185
        for i in range(len(objets)):
186
            self.ajouter_objet(objets[i])
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
187
188

    def supprimer_objet(self, objet):
189
190
191
192
193
194
195
196
        for morph in self.morph_entrants[objet]:
            self.supprimer_morphisme(morph)
        for morph in self.morph_sortants[objet]:
            self.supprimer_morphisme(morph)
        self.supprimer_morphisme(self.identites[objet])
        self.objets.remove(objet)
        if TOUJOURS_VERIFIER_COHERENCE:
            self.verifier_coherence()
197
198
199
200
            
    def supprimer_objets(self,objets):
        for obj in copy.copy(objets):
            self.supprimer_objet(obj)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
201
202
203
204

    def supprimer_morphisme(self, morphisme):
        for elem_a_del in [e for e in self.Composee.loi_de_composition if
                           morphisme in self.Composee.loi_de_composition[e]]:
205
206
207
208
209
210
211
212
213
214
215
216
217
218
            del self.Composee.loi_de_composition[elem_a_del]
        if morphisme in self.Composee.loi_de_composition:
            del self.Composee.loi_de_composition[morphisme]
        self.morphismes.remove(morphisme)
        if not morphisme.is_identite:
            self.morph_entrants[morphisme.cible].remove(morphisme)
            self.morph_sortants[morphisme.source].remove(morphisme)
            while len(self.diagrammes) > 0:
                for i in range(len(self.diagrammes)):
                    d = self.diagrammes[i]
                    if morphisme in [e for compo in d.app_morph.values() for e in compo]:
                        self.diagrammes.pop(i)
                        nouveau_sketch = Categorie()
                        nouveau_sketch.ajouter_objets(d.cat_source.objets)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
219
220
221
222
223
224
                        nouveau_sketch.ajouter_morphismes([morph for morph in d.cat_source.morphismes if
                                                           morphisme not in d.app_morph[
                                                               morph] and not morph.is_identite])
                        nouveau_diagramme = Diagramme.Diagramme(nouveau_sketch, self, d.app_objets,
                                                                {cle: d.app_morph[cle] for cle in
                                                                 nouveau_sketch.morphismes if not cle.is_identite})
225
226
227
228
229
230
                        nouveau_diagramme.faire_commuter()
                        break
                else:
                    break
        if TOUJOURS_VERIFIER_COHERENCE:
            self.verifier_coherence()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
231

232
233
234
235
    def ajouter_morphisme(self, morphisme):
        self.morphismes += [morphisme]
        self.morph_entrants[morphisme.cible] += [morphisme]
        self.morph_sortants[morphisme.source] += [morphisme]
236
237
        if morphisme.is_identite:
            self.remplacer_identite(morphisme)
238
        self.verifier_coherence()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
239

240
241
242
    def ajouter_morphismes(self, morphismes):
        for m in morphismes:
            self.ajouter_morphisme(m)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
243
244

    def remplacer_identite(self, morphisme):
245
246
        """Remplace l'identité d'un objet par le morphisme."""
        if not morphisme.is_identite:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
247
248
            raise Exception(
                "Tentative de remplacer une identite par un morphisme qui n'est pas une identite " + str(morphisme))
249
250
        objet = morphisme.source
        if objet != morphisme.cible:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
251
252
            raise Exception(
                "Tentative de remplacer une identite par un morphisme qui n'est pas une identite." + str(morphisme))
253
254
        self.morphismes.remove(self.identites[objet])
        self.identites[objet] = morphisme
255
256
        self.morph_sortants[objet].remove(morphisme)
        self.morph_entrants[objet].remove(morphisme)
257

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
258
    def enumerer_composees_sans_cycle(self, source, cible, noeuds_deja_visites=tuple()):
259
260
        """Renvoie tous les morphismes composés allant de source à cible ne contenant aucun cycle (on ne passe jamais deux fois par le même noeud).
        Si la loi de composition est mal définie pour un cycle de morphismes, cette fonction ne boucle pas à l'infini."""
261
        if source == cible:
262
263
            return [self.identites[source]]
        if source not in noeuds_deja_visites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
264
            noeuds_deja_visites = noeuds_deja_visites + (source,)
265
266
            composees_resultat = []
            for morph in self.morph_sortants[source]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
267
268
269
                for composition_candidate in self.enumerer_composees_sans_cycle(morph.cible, cible,
                                                                                noeuds_deja_visites):
                    composee = self.Composee(morph, composition_candidate)
270
271
272
273
                    if composee not in composees_resultat:
                        composees_resultat += [composee]
            return composees_resultat
        return []
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
274
275

    def trouver_cycles_elementaires(self, objet):
276
277
        """Renvoie tous les cycles de morphismes élémentaires (qui ne contiennent aucun cycle) 
        de objet à objet qui n'est pas l'identité."""
278
279
280
        cycles = []
        for morph_pred in self.morph_entrants[objet]:
            pred = morph_pred.source
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
281
            cycles_tronques = self.enumerer_composees_sans_cycle(objet, pred)
282
            for cycle_tronque in cycles_tronques:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
283
                cycle = self.Composee(cycle_tronque, morph_pred)
284
285
                if cycle not in cycles:
                    cycles += [cycle]
286
        return cycles
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
287
288

    def enumerer_cycles(self, objet):
289
        """Enumère toutes les compositions de objet à objet qui ne sont pas l'identité.
290
291
292
293
294
        Si f et g sont des cycles élémentaires, on doit énumérer tous les mots d'alphabet {f,g}.
        Pour ça on s'intéresse aux compositions qui se réduisent en composition avec un nombre de morphismes strictement inférieurs.
        Si f ne se réduit pas, on regarde ff et fg, puis si ceux là non plus fff, ffg, fgf,fgg etc récursivement.
        """
        cycles = self.trouver_cycles_elementaires(objet)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
295
296

        def trouver_reductions(mot=tuple()):
297
            if DEBUG_LOI_DE_COMPOSITION:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
298
299
                print(' , '.join(map(str, mot)))
                print(list(map(lambda x: cycles.index(x), mot)))
300
            if len(mot) > 0:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
301
                # composition = Composition(*mot)
302
                composee = self.Composee(*mot)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
303
                if len(composee) < len(mot) or composee.is_identite:  # l'identité est une composition de 0 morphisme
304
305
306
307
                    return [composee]
                resultat = [composee]
            else:
                resultat = []
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
308

309
            for c in cycles:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
310
                reductions = trouver_reductions(mot + (c,))
311
312
                resultat += [e for e in reductions if e not in resultat]
            return resultat
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
313

314
        return [morph for morph in trouver_reductions() if not morph.is_identite]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
315
316

    def enumerer_composees(self, source, cible):
317
318
319
320
321
        """Renvoie tous les morphismes composés allant de source à cible.
        Si la loi de composition est mal définie pour un cycle de morphismes, cette fonction boucle à l'infini.
        1) On trouve les chemins sans cycle.
        2) Pour tous les noeuds U du chemin on énumère les cycles de U à U.
        3) Pour chaque chemin sans cycle, pour chaque noeud qui a au moins un cycle, on duplique le chemin d'autant de cycles que nécessaires."""
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
322
        chemins_sans_cycles = self.enumerer_composees_sans_cycle(source, cible)
323
        noeuds = list(set([e for c in chemins_sans_cycles for e in c.objets_traverses()]))
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
324
        cycles = list(map(lambda x: self.enumerer_cycles(x), noeuds))
325
        tous_les_chemins = []
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
326

327
328
329
330
331
332
        for c in chemins_sans_cycles:
            noeuds_impliques = []
            cycles_impliques = []
            for noeud in c.objets_traverses():
                if len(cycles[noeuds.index(noeud)]) > 0:
                    noeuds_impliques += [noeud]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
333
334
                    cycles_impliques += [[self.identites[noeud]] + cycles[
                        noeuds.index(noeud)]]  # on rajoute une identité pour la possibilité de pas rajouter le cycle
335
336
337
338
339
340
            if len(cycles_impliques) > 0:
                for prod in itertools.product(*cycles_impliques):
                    morphismes = []
                    k = 0
                    for i in range(len(noeuds_impliques)):
                        noeud = noeuds_impliques[i]
341
342
                        while k < len(c) and c[k].source != noeud:
                            morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
343
                            k += 1
344
                        morphismes += [prod[i]]
345
                    while k < len(c):
346
                        morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
347
                        k += 1
348
349
350
351
                    comp = self.Composee(*morphismes)
                    if comp not in tous_les_chemins:
                        tous_les_chemins += [comp]
            else:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
352
                tous_les_chemins += [c]
353
354
        return tous_les_chemins

355
    def enumerer_composees_sortantes(self, source):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
356
357
        return [morph for cible in self.objets for morph in self.enumerer_composees(source, cible)]

358
359
360
    def enumerer_toutes_composees(self):
        """Renvoie un dictionnaire de la forme (source,cible):[composees]."""
        result = dict()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
361
362
363
364
        cycles_noeuds = {noeud: [self.identites[noeud]] + self.enumerer_cycles(noeud) for noeud in self.objets}
        for couple in itertools.product(self.objets, repeat=2):
            source, cible = couple
            chemins_sans_cycles = self.enumerer_composees_sans_cycle(source, cible)
365
366
            noeuds = list(set([e for c in chemins_sans_cycles for e in c.objets_traverses()]))
            tous_les_chemins = []
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
367

368
369
370
371
372
373
            for c in chemins_sans_cycles:
                noeuds_impliques = []
                cycles_impliques = []
                for noeud in c.objets_traverses():
                    if len(cycles_noeuds[noeud]) > 0:
                        noeuds_impliques += [noeud]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
374
                        cycles_impliques += [cycles_noeuds[noeud]]  # on rajoute une identité pour la possibilité de pas rajouter le cycle
375
376
377
378
379
380
                if len(cycles_impliques) > 0:
                    for prod in itertools.product(*cycles_impliques):
                        morphismes = []
                        k = 0
                        for i in range(len(noeuds_impliques)):
                            noeud = noeuds_impliques[i]
381
382
                            while k < len(c) and c[k].source != noeud:
                                morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
383
                                k += 1
384
                            morphismes += [prod[i]]
385
                        while k < len(c):
386
                            morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
387
                            k += 1
388
389
390
391
                        comp = self.Composee(*morphismes)
                        if comp not in tous_les_chemins:
                            tous_les_chemins += [comp]
                else:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
392
                    tous_les_chemins += [c]
393
            result[couple] = tous_les_chemins
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
394
395
        return result

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
396
397
398
399
    def est_cyclique(self):
        """Renvoie un booléen qui indique si le graphe sous-jacent est cyclique"""
        noeuds_suspects = []
        noeuds_safe = []
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
400

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
        def marquer_descendants(noeud):
            """Renvoie true si cycle trouvé, false sinon.
            Ajoute le noeud aux noeuds suspects et étend la liste des noeuds suspects en propageant aux descendants."""
            nonlocal noeuds_suspects
            nonlocal noeuds_safe
            if noeud in noeuds_suspects:
                return True
            if noeud in noeuds_safe:
                return False
            noeuds_suspects += [noeud]
            for morph in self.morph_sortants[noeud]:
                succ = morph.cible
                if marquer_descendants(succ):
                    return True
            noeuds_suspects.remove(noeud)
            noeuds_safe += [noeud]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
417

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
418
419
420
421
422
423
424
425
        noeuds_non_visites = [e for e in self.objets if e not in noeuds_safe]
        while len(noeuds_non_visites) > 0:
            noeud = noeuds_non_visites[0]
            if marquer_descendants(noeud):
                return True
            noeuds_safe += noeuds_suspects
            noeuds_suspects = []
            noeuds_non_visites = [e for e in self.objets if e not in noeuds_safe]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
426

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
427
        return False
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
428
429

    def nb_composees_borne_sup(self, source, cible, noeuds_deja_visites=tuple()):
430
431
432
433
434
435
436
        """Renvoie une borne sup du nombre de composées entre source et cible.
        Dans le cas acyclique, borne_inf = borne_sup.
        Boucle à l'infini si la loi de composition est mal définie."""
        if source in noeuds_deja_visites:
            return 0
        noeuds_deja_visites += tuple([source])
        if source == cible:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
437
            return 1 + len(self.enumerer_cycles(source))
438
        if len(self.morph_sortants[source]) > 0:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
439
440
            result = sum([self.nb_composees_borne_sup(morph.cible, cible, noeuds_deja_visites) for morph in
                          self.morph_sortants[source]])
441
            for c in self.enumerer_cycles(source):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
442
443
444
                noeuds_deja_visites2 = noeuds_deja_visites + tuple([m.cible for m in c])
                result += sum([self.nb_composees_borne_sup(morph.cible, cible, noeuds_deja_visites2) for morph in
                               self.morph_sortants[source]])
445
446
            return result
        return 0
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
447
448

    def nb_composees_borne_inf(self, source, cible, noeuds_deja_visites=tuple()):
449
450
451
452
453
454
455
456
        """Renvoie une borne inf du nombre de composées entre source et cible.
        Dans le cas acyclique, borne_inf = borne_sup."""
        if source in noeuds_deja_visites:
            return 0
        noeuds_deja_visites += tuple([source])
        if source == cible:
            return 1
        if len(self.morph_sortants[source]) > 0:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
457
458
            return sum([self.nb_composees_borne_inf(morph.cible, cible, noeuds_deja_visites) for morph in
                        self.morph_sortants[source]])
459
        return 0
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
460

461
    def compter_toutes_composees(self):
462
463
464
        """Renvoie un intervalle qui contient le nombre de composées de toute la catégorie.
        Dans le cas acyclique, borne_inf = borne_sup.
        Boucle à l'infini si la loi de composition est mal définie."""
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
465
466
467
        return (sum([self.nb_composees_borne_inf(a, b) for a, b in itertools.product(self.objets, repeat=2)]),
                sum([self.nb_composees_borne_sup(a, b) for a, b in itertools.product(self.objets, repeat=2)]))

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
    def table_loi_de_composition(self):
        """Renvoie un dico de dico tel que table[f][g] = g o f, table[f][g] = None si f et g ne sont pas composables.
        La table contient les compositions triviales f o Id = f, f o g = f o g, f o (g o h) = (f o g) o h etc.
        Appel à  enumerer_toutes_composees, préferer accéder au dico Composee.loi_de_composition si possible."""
        table = defaultdict(defaultdict)
        composees = self.enumerer_toutes_composees()
        fleches = list(set([e for liste in composees.values() for e in liste]))
        print(fleches)
        for f,g in itertools.product(fleches, repeat=2):
            if f.cible == g.source:
                table[f][g] = self.Composee(f,g)
        return table

    def pretty_print_loi_de_composition(self,destination=None):
        """Si destination == None, alors on le print sinon on l'écrit dans un fichier"""
        composees = self.enumerer_toutes_composees()
        fleches = list(set([e for liste in composees.values() for e in liste]))
        fleches.sort(key = str)
        fleches.sort(key = lambda x:len(str(x)))
        result = "\t"+'\t'.join(map(lambda x:str(x).replace(' >> ','>'),fleches))+"\n"
        for f in fleches:
            result += str(f).replace(' >> ','>')+"\t"
            for g in fleches:
                if f.cible == g.source:
                    result += str(self.Composee(f, g)).replace(' >> ','>')+"\t"
                else:
                    result += "X\t"
            result += '\n'
        if destination != None:
            with open(destination, 'w') as f:
                f.write(result)
        else:
            print(result)

502
    def pretty_print(self):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
503
504
505
        result = "Objets de la categorie : \n" + ', '.join(
            map(str, self.objets)) + "\n\nMorphismes de la categorie : \n"
        result += '\n'.join(map(lambda x: x.pretty_print(), self.morphismes)) + "\n\n"
506
        return result
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
507
508

    def transformer_graphviz(self, complet=True, objets=None, destination=None, afficher_identites=False):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
509
510
        """Permet de visualiser la catégorie avec graphviz"""
        Categorie.nb_viz += 1
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
511
        if destination == None:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
512
            destination = "graphviz/categorie" + str(Categorie.nb_viz)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
513

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
514
        graph = Digraph('categorie')
515
        graph.attr(concentrate="true" if GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
516
        graph.attr(label=self.nom)
517
518
        if objets == None:
            objets = self.objets
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
519

520
521
        for o in objets:
            graph.node(str(o))
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
522
        if complet:
523
            fleches = self.enumerer_toutes_composees()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
524
            for couple in itertools.product(objets, objets):
525
526
                composees = fleches[couple]
                for c in composees:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
527
528
                    if afficher_identites or not c.is_identite:
                        if c in self.morphismes:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
529
                            graph.edge(str(couple[0]), str(couple[1]), label=str(c.representant), weight="1000")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
530
                        else:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
531
                            graph.edge(str(couple[0]), str(couple[1]), label=str(c.representant), color="grey77")
532
533
        else:
            for morphisme in self.morphismes:
534
                if afficher_identites or not morphisme.is_identite:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
535
                    graph.edge(str(morphisme.source), str(morphisme.cible), label=str(morphisme.representant))
536
        graph.render(destination)
537
538
539
        if CLEAN_GRAPHVIZ_MODEL:
            import os
            os.remove("graphviz/categorie" + str(Categorie.nb_viz))
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
540
541


542
def main():
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
    cat = Categorie("Catégorie acyclique")
    cat.ajouter_objets("ABCDEF")
    f,g,h,i,j,k = [Morphisme('A','B','f'),Morphisme('A','C','g'),Morphisme('B','D','h'),Morphisme('B','E','i'),
                       Morphisme('C','E','j'),Morphisme('C','F','k')]
    cat.ajouter_morphismes([f,g,h,i,j,k])
    cat.transformer_graphviz()
    cat4 = cat.__copy__()

    diag = Diagramme.Carre(cat,"ABCE",[f,i,g,j])
    diag.faire_commuter()
    diag.transformer_graphviz()
    cat.transformer_graphviz()

    m = Morphisme('B', 'C', 'm')
    cat4.ajouter_morphisme(m)
    import CategoriePreordre
    cat4 = CategoriePreordre.CategoriePreordre(cat4)

    a = Morphisme('A','A','a')
    cat.ajouter_morphisme(a)
    cat4.ajouter_morphisme(a)
    diag = Diagramme.Triangle(cat,"AAA",[a,a,a])
    diag2 = Diagramme.Triangle(cat4,"AAA",[a,a,a])
    diag.faire_commuter()
    diag2.faire_commuter()
    cat.nom = "Catégorie"
    cat4.nom = "Catégorie"
    cat.transformer_graphviz()

    b = Morphisme('E','A','b')
    cat.ajouter_morphisme(b)
    diag = Diagramme.Triangle(cat, "AAA", [cat.Composee(f,i,b), cat.Composee(f,i,b), cat.Composee(f,i,b)])
    diag.faire_commuter()
    diag = Diagramme.Triangle(cat, "AAA", [cat.Composee(f,i,b), a, cat.identites['A']])
    diag.faire_commuter()
    cat.transformer_graphviz()

    cat.transformer_graphviz(False)
    import CategoriePreordre
    cat = CategoriePreordre.CategoriePreordre(cat)
    cat.transformer_graphviz()

    cat.diagrammes[-1].transformer_graphviz()

    cat2 = Categorie("1 flèche")
    cat2.ajouter_objets("XY")
    z = Morphisme('X','Y','z')
    cat2.ajouter_morphisme(z)

    import Foncteur
    cat4.transformer_graphviz()
    fonct = Foncteur.Foncteur(cat2,cat4,{'X':'C','Y':'F'},{z:k})
    fonct.transformer_graphviz()

    for obj in cat.objets:
        cones = fonct.enumerer_cones(obj)
        for c in cones:
            c.transformer_graphviz()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
601
602
    from CategorieCones import CategorieCones
    cat3 = CategorieCones(fonct)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
    cat3.transformer_graphviz()

    cat = Categorie("Test table loi de composition")
    cat.ajouter_objets("ABCDE")
    f, g, h, i, j, k, l, m = [Morphisme('A', 'B', 'f'), Morphisme('A', 'E', 'g'), Morphisme('A', 'E', 'h'),
                              Morphisme('B', 'E', 'i'),
                              Morphisme('C', 'D', 'j'), Morphisme('B', 'E', 'k'), Morphisme('C', 'A', 'l'),
                              Morphisme('D', 'E', 'm')]
    cat.ajouter_morphismes([f, g, h, i, j, k, l, m])
    cat.transformer_graphviz()
    import CategoriePreordre
    cat2 = CategoriePreordre.CategoriePreordre(cat)
    cat2.transformer_graphviz()
    n = Morphisme('A', 'A', 'n')
    cat2.ajouter_morphisme(n)
    diag = Diagramme.Triangle(cat2, "AAA", [Composition(n,n), n, n])
    diag.faire_commuter()
    cat2.transformer_graphviz()


623
624
625
626
627
628
629
630
631
632
633
634
    # cat = Categorie("Acyclique")
    # cat.ajouter_objets("ABCDEF")
    # f,g,h,i,j,k,l = [Morphisme('A','B','f'),Morphisme('A','C','g'),Morphisme('A','E','h'),Morphisme('B','D','i'),Morphisme('B','E','j'),Morphisme('C','E','k'),Morphisme('C','F','l')]
    # cat.ajouter_morphismes([f,g,h,i,j,k,l])
    # cat.transformer_graphviz()
    # t = Diagramme.Triangle(cat,"ABE",[f,j,h])
    # t.faire_commuter()
    # cat.transformer_graphviz()
    # t = Diagramme.Triangle(cat,"ACE",[g,k,h])
    # t.faire_commuter()
    # cat.transformer_graphviz()
    # print("est_cyclique : "+str(cat.est_cyclique()))
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
635

636
637
638
639
640
641
    # cat = Categorie("Test Isomorphisme")
    # cat.ajouter_objets("ABC")
    # f,g,h,i = [Morphisme('A','B','f'),Morphisme('B','A','g'),Morphisme('A','C','h'),Morphisme('C','A','i')]
    # cat.ajouter_morphismes([f,g,h,i])
    # t = Diagramme.Triangle(cat,"ABA",[f,g,cat.identites['A']])
    # t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
642
643
    # t = Diagramme.Triangle(cat,"ACA",[h,i,cat.identites['A']])
    # t.faire_commuter()
644
645
646
647
648
649
650
    # t = Diagramme.Triangle(cat,"BAB",[g,f,cat.identites['B']])
    # t.faire_commuter()
    # t = Diagramme.Triangle(cat,"CAC",[cat.Composee(i,h,i),h,cat.Composee(i,h)])
    # t.faire_commuter()
    # cat.transformer_graphviz()
    # t.transformer_graphviz()
    # print("est_cyclique : "+str(cat.est_cyclique()))
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
651

652
    cat = Categorie("Test cycles")
653
    cat.ajouter_objets("ABCDE")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
654
655
656
657
658
659
    a1, a2, a3 = [Morphisme('A', 'B', 'a1'), Morphisme('B', 'C', 'a2'), Morphisme('C', 'A', 'a3')]
    b1, b2, b3 = [Morphisme('A', 'D', 'b1'), Morphisme('D', 'E', 'b2'), Morphisme('E', 'A', 'b3')]
    cat.ajouter_morphismes([a1, a2, a3, b1, b2, b3])
    a = cat.Composee(a1, a2, a3)
    b = cat.Composee(b1, b2, b3)
    t = Diagramme.Triangle(cat, "AAA", [a, cat.identites['A'], cat.identites['A']])
660
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
661
    t = Diagramme.Triangle(cat, "AAA", [b, cat.identites['A'], cat.identites['A']])
662
    t.faire_commuter()
663
664
665
666
    # t = Diagramme.Triangle(cat,"AAA",[c,cat.identites['A'],cat.identites['A']])
    # t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[a,c,cat.identites['A']])
    # t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
667
    t = Diagramme.Triangle(cat, "AAA", [a, b, cat.identites['A']])
668
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
669
    t = Diagramme.Triangle(cat, "AAA", [b, a, cat.identites['A']])
670
    t.faire_commuter()
671
672
673
674
675
676
    # t = Diagramme.Triangle(cat,"AAA",[c,a,cat.identites['A']])
    # t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[b,c,cat.identites['A']])
    # t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[c,b,cat.identites['A']])
    # t.faire_commuter()
677
678
    cat.transformer_graphviz()
    print(cat.enumerer_cycles('D')[0])
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
679
680

    print("est_cyclique : " + str(cat.est_cyclique()))
681
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
682
683
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

684
685
686
687
688
    # cat = Categorie("Test")
    # cat.ajouter_objets("AB")
    # f,g = [Morphisme('A','B','f'),Morphisme('A','B','g')]
    # cat.ajouter_morphismes([f,g])
    # cat.transformer_graphviz()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
689

690
691
692
693
    # diag = Diagramme.DiagrammeIdentite(cat)
    # diag.faire_commuter()
    # print(cat.morph_sortants)
    # cat.transformer_graphviz()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
694

695
696
    cat = Categorie("Test comptage composees")
    cat.ajouter_objets("ABCDE")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
697
698
699
700
701
    f, g, h, i, j, k, l, m = [Morphisme('A', 'B', 'f'), Morphisme('A', 'E', 'g'), Morphisme('A', 'E', 'h'),
                              Morphisme('B', 'E', 'i'),
                              Morphisme('C', 'D', 'j'), Morphisme('B', 'E', 'k'), Morphisme('C', 'A', 'l'),
                              Morphisme('D', 'E', 'm')]
    cat.ajouter_morphismes([f, g, h, i, j, k, l, m])
702
    cat.transformer_graphviz()
703
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
704
705
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

706
707
    cat = Categorie("Test comptage composees")
    cat.ajouter_objets("ABCD")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
708
709
710
711
712
    f, g, h, i, j, k = [Morphisme('A', 'B', 'f'), Morphisme('B', 'A', 'g'), Morphisme('A', 'C', 'h'),
                        Morphisme('C', 'B', 'i'),
                        Morphisme('B', 'D', 'j'), Morphisme('C', 'D', 'k')]
    cat.ajouter_morphismes([f, g, h, i, j, k])
    diag = Diagramme.Triangle(cat, "AAA", [cat.Composee(f, g), cat.Composee(f, g), cat.Composee(f, g)])
713
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
714
    diag = Diagramme.Triangle(cat, "AAA", [cat.Composee(h, i, g), cat.identites['A'], cat.identites['A']])
715
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
716

717
718
    cat.transformer_graphviz()
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
719
720
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

721
722
    cat = Categorie("Test comptage composees borne sup atteinte")
    cat.ajouter_objets("ABC")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
723
724
725
726
    f, g, h, i, j = [Morphisme('A', 'A', 'f'), Morphisme('A', 'B', 'g'), Morphisme('B', 'B', 'h'),
                     Morphisme('B', 'C', 'i'), Morphisme('C', 'C', 'j')]
    cat.ajouter_morphismes([f, g, h, i, j])
    diag = Diagramme.Triangle(cat, "AAA", [cat.Composee(f), cat.Composee(f), cat.Composee(f)])
727
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
728
    diag = Diagramme.Triangle(cat, "BBB", [cat.Composee(h), cat.Composee(h), cat.Composee(h)])
729
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
730
    diag = Diagramme.Triangle(cat, "CCC", [cat.Composee(j), cat.Composee(j), cat.Composee(j)])
731
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
732

733
734
    cat.transformer_graphviz()
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
735
736
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

737
738
    cat.transformer_graphviz()
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
739
740
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

741
742
    cat = Categorie("Test cycles")
    cat.ajouter_objets("ABC")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
743
744
745
746
747
748
    a1, a2 = [Morphisme('A', 'B', 'a1'), Morphisme('B', 'A', 'a2')]
    b1, b2 = [Morphisme('A', 'C', 'b1'), Morphisme('C', 'A', 'b2')]
    cat.ajouter_morphismes([a1, a2, b1, b2])
    a = cat.Composee(a1, a2)
    b = cat.Composee(b1, b2)
    t = Diagramme.Triangle(cat, "AAA", [a, a, cat.identites['A']])
749
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
750
    t = Diagramme.Triangle(cat, "AAA", [b, b, cat.identites['A']])
751
752
753
754
755
    t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[c,cat.identites['A'],cat.identites['A']])
    # t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[a,c,cat.identites['A']])
    # t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
756
    t = Diagramme.Triangle(cat, "AAA", [a, b, cat.identites['A']])
757
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
758
    t = Diagramme.Triangle(cat, "AAA", [b, a, cat.identites['A']])
759
760
761
762
763
764
765
766
    t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[c,a,cat.identites['A']])
    # t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[b,c,cat.identites['A']])
    # t.faire_commuter()
    # t = Diagramme.Triangle(cat,"AAA",[c,b,cat.identites['A']])
    # t.faire_commuter()
    cat.transformer_graphviz()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
767
768
769
    print(cat.nb_composees_borne_sup("A", "A"))
    print(len(cat.enumerer_toutes_composees()[("A", "A")]))

770
if __name__ == '__main__':
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
771
    main()