Categorie.py 35.5 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
        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)

32
33
34
        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
35
36
37
38
39
            loi_de_composition = {}  # dico Composition:Composition

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

            def verifier_coherence(noeuds_visites=tuple()):
40
41
                for noeud in Composee.loi_de_composition:
                    if noeud not in noeuds_visites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
42
43
                        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:
44
                            raise Exception("Loi de composition associe deux fleches elementaires")
45
                        if len(noeud) < len(Composee.loi_de_composition[noeud]):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
46
47
48
                            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]))
49
                        noeuds_visites += tuple([noeud])
50
                        if Composee.loi_de_composition[noeud] in noeuds_visites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
51
52
53
                            raise Exception(
                                "Loi de composition n'est pas acyclique : " + str(Composition(*noeud)) + " -> " + str(
                                    Composee.loi_de_composition[noeud]))
54
                        Composee.verifier_coherence(noeuds_visites)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
55

56
            def _simplifier_composee(morphismes):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
57
58
59
60
                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])
61
                        if sous_composee in self.Composee.loi_de_composition:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
62
63
64
65
                            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
66
67

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

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

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

82
    def __copy__(self):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
83
        newone = Categorie(self.nom)
84
85
86
        newone.objets = copy.copy(self.objets)
        newone.morphismes = copy.copy(self.morphismes)
        newone.identites = copy.copy(self.identites)
87
88
89
90
91
92
        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])
93
        newone.Composee.loi_de_composition = copy.copy(self.Composee.loi_de_composition)
94
        return newone
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
95
96

    def __eq__(self, other):
97
98
        if not issubclass(type(other),Categorie):
            return False
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
99
100
        return self.objets == other.objets and self.morphismes == other.morphismes and \
               self.identites == other.identites and self.morph_sortants == other.morph_sortants \
101
               and self.morph_entrants == other.morph_entrants\
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
102
103
               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
113
114
        if source == cible and inclure_id:
            return [morph for morph in self.morph_sortants[source] if morph.cible == cible]+[self.identites[source]] 
        return [morph for morph in self.morph_sortants[source] if morph.cible == cible]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
115

116
117
118
119
120
    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
121
            assert (len(m) == len(self.morph_sortants) == len(self.morph_entrants) == len(self.Composee.loi_de_composition) == 0)
122
123
124
        else:
            ## On vérifie l'existence de l'identité pour tous les objets (O(n))
            for o in self.objets:
125
                if o not in self.identites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
126
127
                    raise Exception("Pas de morphisme identite pour l'objet " + str(o))

128
129
            ## 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
130
131
            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
132
133
134
                    raise Exception(
                        "Morphismes identifies avec des sources differentes : " + ''.join(map(str, m)) + "," + str(
                            self.Composee.loi_de_composition[m]))
135
                if morph.cible != self.Composee.loi_de_composition[morph].cible:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
136
137
138
139
                    raise Exception(
                        "Morphismes identifies avec des cibles differentes : " + ''.join(map(str, m)) + "," + str(
                            self.Composee.loi_de_composition[m]))

140
141
142
143
            ## 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
144
                raise Exception("Trop d'identites : " + ','.join(map(str, ident)))
145

146
147
148
149
150
151
            ## 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
152
153
                                # print(' , '.join(map(str,self.morphismes)))
                                print(' , '.join(map(str, m)))
154
                                print(morph)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
155
                                raise Exception("Erreur morph_sortants : morphisme manquant " + str(morph))
156
157
                        if morph.source != obj:
                            if morph in self.morph_sortants[obj]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
158
159
                                raise Exception("Erreur morph_sortants : morphisme en trop " + str(morph))

160
161
162
163
164
165
            ## 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
166
                                raise Exception("Erreur morph_entrants : morphisme manquant " + str(morph))
167
168
                        if morph.cible != obj:
                            if morph in self.morph_entrants[obj]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
169
                                raise Exception("Erreur morph_entrants : morphisme en trop " + str(morph))
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
170
171
            if TOUJOURS_VERIFIER_COHERENCE_COMPOSEE:
                self.Composee.verifier_coherence()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
172

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

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

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

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

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

    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]]:
209
210
211
            del self.Composee.loi_de_composition[elem_a_del]
        if morphisme in self.Composee.loi_de_composition:
            del self.Composee.loi_de_composition[morphisme]
212
213
214
215
        for elem_a_del in [ante for ante,im in self.Composee.loi_de_composition.items() if morphisme in im]:
            del self.Composee.loi_de_composition[ante]
                
                
216
217
218
219
220
221
        self.morphismes.remove(morphisme)
        if not morphisme.is_identite:
            self.morph_entrants[morphisme.cible].remove(morphisme)
            self.morph_sortants[morphisme.source].remove(morphisme)
        if TOUJOURS_VERIFIER_COHERENCE:
            self.verifier_coherence()
222
223
224
225
    
    def supprimer_morphismes(self, morphismes):
        for morph in copy.copy(morphismes):
            self.supprimer_morphisme(morph)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
226

227
228
229
230
    def ajouter_morphisme(self, morphisme):
        self.morphismes += [morphisme]
        self.morph_entrants[morphisme.cible] += [morphisme]
        self.morph_sortants[morphisme.source] += [morphisme]
231
232
        if morphisme.is_identite:
            self.remplacer_identite(morphisme)
233
        self.verifier_coherence()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
234

235
236
237
    def ajouter_morphismes(self, morphismes):
        for m in morphismes:
            self.ajouter_morphisme(m)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
238
239

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

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
253
    def enumerer_composees_sans_cycle(self, source, cible, noeuds_deja_visites=tuple()):
254
255
        """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."""
256
        if source == cible:
257
258
            return [self.identites[source]]
        if source not in noeuds_deja_visites:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
259
            noeuds_deja_visites = noeuds_deja_visites + (source,)
260
261
            composees_resultat = []
            for morph in self.morph_sortants[source]:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
262
263
264
                for composition_candidate in self.enumerer_composees_sans_cycle(morph.cible, cible,
                                                                                noeuds_deja_visites):
                    composee = self.Composee(morph, composition_candidate)
265
266
267
268
                    if composee not in composees_resultat:
                        composees_resultat += [composee]
            return composees_resultat
        return []
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
269
270

    def trouver_cycles_elementaires(self, objet):
271
272
        """Renvoie tous les cycles de morphismes élémentaires (qui ne contiennent aucun cycle) 
        de objet à objet qui n'est pas l'identité."""
273
274
275
        cycles = []
        for morph_pred in self.morph_entrants[objet]:
            pred = morph_pred.source
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
276
            cycles_tronques = self.enumerer_composees_sans_cycle(objet, pred)
277
            for cycle_tronque in cycles_tronques:
278
                cycle = self.Composee(cycle_tronque, morph_pred) #todo : déterminer s'il faut mettre Composition ou Composee
279
280
                if cycle not in cycles:
                    cycles += [cycle]
281
        return cycles
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
282
283

    def enumerer_cycles(self, objet):
284
        """Enumère toutes les compositions de objet à objet qui ne sont pas l'identité.
285
286
287
288
289
        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
290
291

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

304
            for c in cycles:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
305
                reductions = trouver_reductions(mot + (c,))
306
307
                resultat += [e for e in reductions if e not in resultat]
            return resultat
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
308

309
        return [morph for morph in trouver_reductions() if not morph.is_identite]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
310
311

    def enumerer_composees(self, source, cible):
312
313
314
315
316
        """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
317
        chemins_sans_cycles = self.enumerer_composees_sans_cycle(source, cible)
318
        noeuds = list(set([e for c in chemins_sans_cycles for e in c.objets_traverses()]))
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
319
        cycles = list(map(lambda x: self.enumerer_cycles(x), noeuds))
320
        tous_les_chemins = []
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
321

322
323
324
325
326
327
        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
328
329
                    cycles_impliques += [[self.identites[noeud]] + cycles[
                        noeuds.index(noeud)]]  # on rajoute une identité pour la possibilité de pas rajouter le cycle
330
331
332
333
334
335
            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]
336
337
                        while k < len(c) and c[k].source != noeud:
                            morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
338
                            k += 1
339
                        morphismes += [prod[i]]
340
                    while k < len(c):
341
                        morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
342
                        k += 1
343
344
345
346
                    comp = self.Composee(*morphismes)
                    if comp not in tous_les_chemins:
                        tous_les_chemins += [comp]
            else:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
347
                tous_les_chemins += [c]
348
349
        return tous_les_chemins

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

353
354
355
    def enumerer_toutes_composees(self):
        """Renvoie un dictionnaire de la forme (source,cible):[composees]."""
        result = dict()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
356
357
358
359
        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)
360
361
            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
362

363
364
365
366
367
368
            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
369
                        cycles_impliques += [cycles_noeuds[noeud]]  # on rajoute une identité pour la possibilité de pas rajouter le cycle
370
371
372
373
374
375
                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]
376
377
                            while k < len(c) and c[k].source != noeud:
                                morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
378
                                k += 1
379
                            morphismes += [prod[i]]
380
                        while k < len(c):
381
                            morphismes += [c[k]]
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
382
                            k += 1
383
384
385
386
                        comp = self.Composee(*morphismes)
                        if comp not in tous_les_chemins:
                            tous_les_chemins += [comp]
                else:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
387
                    tous_les_chemins += [c]
388
            result[couple] = tous_les_chemins
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
389
390
        return result

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
391
392
393
394
    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
395

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
        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
412

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
413
414
415
416
417
418
419
420
        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
421

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
422
        return False
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
423
424

    def nb_composees_borne_sup(self, source, cible, noeuds_deja_visites=tuple()):
425
426
427
428
429
430
431
        """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
432
            return 1 + len(self.enumerer_cycles(source))
433
        if len(self.morph_sortants[source]) > 0:
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
434
435
            result = sum([self.nb_composees_borne_sup(morph.cible, cible, noeuds_deja_visites) for morph in
                          self.morph_sortants[source]])
436
            for c in self.enumerer_cycles(source):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
437
438
439
                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]])
440
441
            return result
        return 0
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
442
443

    def nb_composees_borne_inf(self, source, cible, noeuds_deja_visites=tuple()):
444
445
446
447
448
449
450
451
        """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
452
453
            return sum([self.nb_composees_borne_inf(morph.cible, cible, noeuds_deja_visites) for morph in
                        self.morph_sortants[source]])
454
        return 0
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
455

456
    def compter_toutes_composees(self):
457
458
459
        """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
460
461
462
        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
463
464
465
466
467
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
    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)

497
    def pretty_print(self):
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
498
499
500
        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"
501
        return result
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
502
503

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

Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
509
        graph = Digraph('categorie')
510
        graph.attr(concentrate="true" if GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
511
        graph.attr(label=self.nom)
512
513
        if objets == None:
            objets = self.objets
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
514

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


537
def main():
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
538
539
540
541
542
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
    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()


    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
595
596
    from CategorieCones import CategorieCones
    cat3 = CategorieCones(fonct)
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
    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()


617
618
619
620
621
622
623
624
625
626
627
628
    # 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
629

630
631
632
633
634
635
    # 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
636
637
    # t = Diagramme.Triangle(cat,"ACA",[h,i,cat.identites['A']])
    # t.faire_commuter()
638
639
640
641
642
643
644
    # 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
645

646
    cat = Categorie("Test cycles")
647
    cat.ajouter_objets("ABCDE")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
648
649
650
651
652
653
    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']])
654
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
655
    t = Diagramme.Triangle(cat, "AAA", [b, cat.identites['A'], cat.identites['A']])
656
    t.faire_commuter()
657
658
659
660
    # 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
661
    t = Diagramme.Triangle(cat, "AAA", [a, b, cat.identites['A']])
662
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
663
    t = Diagramme.Triangle(cat, "AAA", [b, a, cat.identites['A']])
664
    t.faire_commuter()
665
666
667
668
669
670
    # 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()
671
672
    cat.transformer_graphviz()
    print(cat.enumerer_cycles('D')[0])
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
673
674

    print("est_cyclique : " + str(cat.est_cyclique()))
675
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
676
677
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

678
679
680
681
682
    # 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
683

684
685
686
687
    # diag = Diagramme.DiagrammeIdentite(cat)
    # diag.faire_commuter()
    # print(cat.morph_sortants)
    # cat.transformer_graphviz()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
688

689
690
    cat = Categorie("Test comptage composees")
    cat.ajouter_objets("ABCDE")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
691
692
693
694
695
    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])
696
    cat.transformer_graphviz()
697
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
698
699
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

700
701
    cat = Categorie("Test comptage composees")
    cat.ajouter_objets("ABCD")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
702
703
704
705
706
    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)])
707
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
708
    diag = Diagramme.Triangle(cat, "AAA", [cat.Composee(h, i, g), cat.identites['A'], cat.identites['A']])
709
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
710

711
712
    cat.transformer_graphviz()
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
713
714
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

715
716
    cat = Categorie("Test comptage composees borne sup atteinte")
    cat.ajouter_objets("ABC")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
717
718
719
720
    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)])
721
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
722
    diag = Diagramme.Triangle(cat, "BBB", [cat.Composee(h), cat.Composee(h), cat.Composee(h)])
723
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
724
    diag = Diagramme.Triangle(cat, "CCC", [cat.Composee(j), cat.Composee(j), cat.Composee(j)])
725
    diag.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
726

727
728
    cat.transformer_graphviz()
    print(cat.compter_toutes_composees())
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
729
730
    print(sum(map(len, cat.enumerer_toutes_composees().values())))

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

735
736
    cat = Categorie("Test cycles")
    cat.ajouter_objets("ABC")
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
737
738
739
740
741
742
    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']])
743
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
744
    t = Diagramme.Triangle(cat, "AAA", [b, b, cat.identites['A']])
745
746
747
748
749
    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
750
    t = Diagramme.Triangle(cat, "AAA", [a, b, cat.identites['A']])
751
    t.faire_commuter()
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
752
    t = Diagramme.Triangle(cat, "AAA", [b, a, cat.identites['A']])
753
754
755
756
757
758
759
760
    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
761
762
763
    print(cat.nb_composees_borne_sup("A", "A"))
    print(len(cat.enumerer_toutes_composees()[("A", "A")]))

764
if __name__ == '__main__':
Guillaume Sabbagh's avatar
Guillaume Sabbagh committed
765
    main()