Commit 77f2ed72 authored by Francois Mares's avatar Francois Mares
Browse files
parents f6d96021 ed6d211c
from Categorie import Categorie
from Foncteur import Foncteur
from EnsFinis import EnsFinis,Application
from ProduitGenerateurs import produit_cartesien_generateurs
import itertools
from typing import *
......@@ -18,17 +17,19 @@ class CatFinies(Categorie):
# cat_source et cat_cible sont des catégories, on cherche tous les foncteurs de cat_source vers cat_cible
ens_objets = EnsFinis({cat_source.objets,cat_cible.objets})
for app_obj in ens_objets({cat_source.objets},{cat_cible.objets}):
print(app_obj)
app_fleche_foncteur = dict() # à chaque couple d'objets on associe un ensemble d'applications
for c,d in itertools.product(cat_source.objets, repeat=2):
ens_fleches = EnsFinis({frozenset(cat_source[{c},{d}]),frozenset(cat_cible({app_obj(c)},{app_obj(d)}))})
app_fleches_c_vers_d = ens_fleches({frozenset(cat_source[{c},{d}])},{frozenset(cat_cible({app_obj(c)},{app_obj(d)}))})
app_fleches_c_vers_d = set(ens_fleches({frozenset(cat_source[{c},{d}])},{frozenset(cat_cible({app_obj(c)},{app_obj(d)}))}))
app_fleche_foncteur[(c,d)] = app_fleches_c_vers_d
for applications_fleches in produit_cartesien_generateurs(*app_fleche_foncteur.values()):
if len(app_fleches_c_vers_d) == 0:
break
if len(app_fleche_foncteur[(c,d)]) == 0:
continue
for applications_fleches in itertools.product(*app_fleche_foncteur.values()):
app_fleches = dict()
for app in applications_fleches:
app_fleches.update(app.as_dict())
print(app_fleches)
for f in cat_source[abs(cat_source),abs(cat_source)]:
for g in cat_source[abs(cat_source),{f.source}]:
if f@g in app_fleches and app_fleches[f@g] != app_fleches[f]@app_fleches[g]:
......@@ -37,7 +38,6 @@ class CatFinies(Categorie):
continue
break
else:
print("yield")
yield Foncteur(cat_source,cat_cible,app_obj.as_dict(),app_fleches)
def test_CatFinies():
......
......@@ -4,9 +4,9 @@ import types
from collections import defaultdict
import itertools
from Morphisme import Morphisme
from config import *
import config
import copy
if GRAPHVIZ_ENABLED:
if config.GRAPHVIZ_ENABLED:
from graphviz import Digraph
from typing import *
......@@ -104,6 +104,8 @@ class Categorie:
self.nom = "Categorie "+str(self._id) if nom == None else nom
self._objets = frozenset() # read-only attribute
self |= objets
if config.TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
def __hash__(self) -> int:
return hash(self.objets)
......@@ -132,8 +134,6 @@ class Categorie:
On peut surcharger la méthode __call__ de CategorieLibre pour optimiser les calculs quand c'est pertinent. Il faut alors
s'assurer que la nouvelle méthode renvoie les mêmes morphismes que l'ancienne méthode aurait renvoyée.
Il est aussi recommandé de surcharger decomposition_morphisme pour optimiser les calculs.
Si on surcharge __getitem__, on peut aussi surcharger __iter__ des morphismes associés à la catégorie pour itérer sur les constituants
élémentaires du morphisme.
"""
......@@ -141,16 +141,6 @@ class Categorie:
raise Exception("On s'attend à avoir un couple : "+str(couple_sources_cibles))
return self(*couple_sources_cibles)
def decomposition_morphisme(self, morphisme:Morphisme) -> Generator[Morphisme,None,None]:
"""Renvoie un générateur de morphismes élémentaires qui composés donnent le `morphisme`.
Si __getitem__ n'a pas été surchargé, tous les morphismes sont des morphismes élémentaires.
On le renvoie donc simplement.
Si une classe fille surcharge __getitem__ il faut surcharger decomposition_morphisme aussi."""
if TOUJOURS_VERIFIER_COHERENCE_COMPOSEE:
if morphisme not in self[{morphisme.source},{morphisme.cible}]:
raise Exception("Le morphisme fourni n'est pas un morphisme elementaire et la methode decomposition_morphisme n'a pas ete surchargee : "+str(morphisme))
yield morphisme
def identite(self,objet: Any) -> Morphisme:
"""
......@@ -158,7 +148,7 @@ class Categorie:
Les classes filles doivent l'implémenter tel que si C est une catégorie,
C.identite(objet) renvoie l'identité de l'`objet`.
"""
NotImplementedError("Les categories filles doivent implementer cette methode.")
raise NotImplementedError("Les categories filles doivent implementer cette methode.")
def __str__(self) -> str:
return self.nom
......@@ -200,9 +190,10 @@ class Categorie:
def verifier_coherence(self):
"""Vérifie la cohérence de la structure (tous les axiomes des catégories sont vérifiés)."""
morphismes = sorted(self(self.objets,self.objets))
## On vérifie que les identités sont neutres
for morphisme in self(self.objets,self.objets):
for morphisme in morphismes:
if not (morphisme@self.identite(morphisme.source) == self.identite(morphisme.cible)@morphisme == morphisme):
raise Exception("Incoherence Categorie : le morphisme "+str(morphisme)+" est modifie par une identite.\n"\
+repr(morphisme)+" o "+repr(self.identite(morphisme.source))+" = "+repr(morphisme@self.identite(morphisme.source))+"\n"\
......@@ -210,12 +201,18 @@ class Categorie:
repr(morphisme))
## On vérifie l'associativité
for m1,m2,m3 in itertools.product(sorted(self(self.objets,self.objets)),repeat=3):
for m1,m2,m3 in itertools.product(morphismes,repeat=3):
if m1.source == m2.cible and m2.source == m3.cible:
if (m1@m2)@m3 != m1@(m2@m3):
raise Exception("Incoherence Categorie : associativite pas respectee pour "+str(m1)+", "+str(m2)+", "+str(m3)+\
"\n(m1@m2)@m3 = "+str((m1@m2)@m3)+" m1@(m2@m3) = "+str(m1@(m2@m3))+
"\nm1@m2 = "+str(m1@m2)+" m2@m3 = "+str(m2@m3))#+"\nm2@m1 = "+str(m2@m1)+" m3@m2 = "+str(m3@m2))
## On vérifie que toutes les composées existent
for m1 in morphismes:
for m2 in self({m1.cible},self.objets):
if m2@m1 not in morphismes:
raise Exception("Incoherence Categorie : composee de deux morphismes pas dans la categorie "+str(m2@m1))
def isomorphismes(self, source:Any, cible:Any) -> Generator[tuple,None,None]:
"""Trouve les isomorphismes entre `source` et `cible`.
......@@ -228,6 +225,9 @@ class Categorie:
def existe_morphisme(self, source:Any, cible:Any) -> Union[Morphisme,None]:
"""Renvoie un morphisme de `source` à `cible` s'il existe, renvoie None sinon."""
elem = next(self[{source},{cible}],None)
if elem != None:
return elem
return next(self({source},{cible}),None)
def existe_morphisme_elementaire(self, source:Any, cible:Any) -> Union[Morphisme,None]:
......@@ -288,7 +288,7 @@ class Categorie:
destination = "graphviz/"+type(self).__name__+ str(Categorie.nb_viz)
graph = Digraph('categorie')
graph.attr(concentrate="true" if GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
graph.attr(concentrate="true" if config.GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
graph.attr(label=self.nom)
for o in self.objets:
......@@ -315,12 +315,12 @@ class Categorie:
graph.edge(str(source), str(cible), label=str(fleche), weight="1000", color="grey80" if fleche not in fleches_elem else "black")
nb_fleches += 1
if nb_fleches > limite_fleches:
if WARNING_LIMITE_FLECHES_ATTEINTE:
if config.WARNING_LIMITE_FLECHES_ATTEINTE:
print("Warning : limite fleches entre "+str(source)+" et "+str(cible)+" atteinte.")
break
graph.render(destination)
if CLEAN_GRAPHVIZ_MODEL:
if config.CLEAN_GRAPHVIZ_MODEL:
import os
os.remove(destination)
......@@ -355,6 +355,37 @@ def test_SousCategoriePleine():
sous_cat_pleine = SousCategoriePleine(GC,set("ADE"))
sous_cat_pleine.transformer_graphviz()
class SousCategorie(Categorie):
def __init__(self, categorie_originelle:Categorie, objets_a_conserver:frozenset, morph_a_conserver:frozenset,nom:Union[str,None] = None):
"""`objets_a_conserver` contient tous les objets à conserver.
`morph_a_conserver` contient tous les morphismes à conserver (pas seulement les flèches élémentaires).
Toutes les flèches deviennent élémentaires (on perd la structure de la catégorie initiale).
/!\ Les flèches restantes doivent former une catégorie, si deux flèches sont composables alors la composée doit être dans `morph_a_conserver` /!\ """
self._cat_origin = categorie_originelle
self._morph = morph_a_conserver
Categorie.__init__(self,objets_a_conserver,nom if nom != None else "Sous catégorie de "+str(categorie_originelle))
def identite(self,objet:Any) -> Morphisme:
assert(objet in self.objets)
return self._cat_origin.identite(objet)
def __call__(self, sources: set, cibles: set) -> Generator[Morphisme,None,None]:
assert(len(sources-self.objets) == 0 and len(cibles-self.objets) == 0)
for morph in self._cat_origin(sources,cibles):
if morph in self._morph or morph.is_identite:
yield morph
def test_SousCategorie():
from GrapheDeComposition import GC,MGC
GC = GC(set("ABCDE"))
morphismes = [MGC('A','B','f'),MGC('B','C','g'),MGC('A','D','h'),MGC('D','E','i'),MGC('C','E','j')]
GC |= morphismes
GC.transformer_graphviz()
sous_cat = SousCategorie(GC,set("ABDE"),{morphismes[0],morphismes[2],morphismes[3],morphismes[3]@morphismes[2]})
sous_cat.transformer_graphviz()
class CategorieDiscrete(Categorie):
"""
Catégorie discrète : catégorie dont les seuls morphismes sont des identités.
......@@ -394,6 +425,7 @@ def test_CategorieDiscrete():
if __name__ == '__main__':
test_Categorie()
test_CategorieDiscrete()
test_SousCategoriePleine()
\ No newline at end of file
# test_Categorie()
# test_CategorieDiscrete()
# test_SousCategoriePleine()
test_SousCategorie()
\ No newline at end of file
from Categorie import Categorie
from CategorieQuotient import CategorieQuotient
from Morphisme import Morphisme
import itertools
class CategorieAcyclique(CategorieQuotient):
"""`CategorieAcyclique` peut être abrégé en `CatAcyclique`.
Catégorie quotientée par la relation d'équivalence sur les objets suivante :
x ~ y ssi il existe f: x -> y et il existe g: y -> x."""
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
if nom == None:
nom = "Catégorie acyclique engendrée par "+str(categorie_a_quotienter)
CategorieQuotient.__init__(self,categorie_a_quotienter,nom)
for obj1, obj2 in itertools.product(categorie_a_quotienter.objets,repeat=2):
if obj1 != obj2:
if categorie_a_quotienter.existe_morphisme(obj1,obj2) and categorie_a_quotienter.existe_morphisme(obj2,obj1):
self.identifier_morphismes(categorie_a_quotienter.identite(obj1),categorie_a_quotienter.identite(obj2))
for m in categorie_a_quotienter({obj1},{obj2}):
self.identifier_morphismes(m,categorie_a_quotienter.identite(obj2))
for m in categorie_a_quotienter({obj2},{obj1}):
self.identifier_morphismes(m,categorie_a_quotienter.identite(obj2))
else:
for m in categorie_a_quotienter({obj1},{obj1}):
self.identifier_morphismes(m,categorie_a_quotienter.identite(obj1))
def tri_topologique(self) -> list:
"""Renvoie une liste d'objets de la catégorie dans l'ordre topologique.
Si a vient avant b dans la liste alors il n'existe aucune flèche de b vers a.
La construction suit le principe suivant : on prend tous les objets sans pères, on les ajoute au tri,
puis on les retire et on recommence jusqu'à ce qu'il n'y ait plus d'objets à trier.
"""
objets_a_trier = self.objets
tri = []
while len(objets_a_trier) > 0:
nouveaux_objets_tries = set()
for obj in objets_a_trier:
# on cherche à savoir si obj a un père dans la liste des objets à trier
# si c'est le cas, il n'y a aucune flèche dans self(objets_a_trier-{obj},{obj})
if next(self(objets_a_trier-{obj},{obj}),None) == None:
tri += [obj]
nouveaux_objets_tries |= {obj}
objets_a_trier -= nouveaux_objets_tries
return tri
CatAcyclique = CategorieAcyclique
def test_CategorieAcyclique():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(1)
for i in range(1):
c = GrapheCompositionAleatoire(20,150)
c.transformer_graphviz()
c_a = CategorieAcyclique(c)
c_a.transformer_graphviz()
c_a.fonct_surjection.transformer_graphviz()
for obj in c_a.objets:
c_a.sous_cat_equiv(obj).transformer_graphviz()
print(c_a.tri_topologique())
if __name__ == '__main__':
test_CategorieAcyclique()
\ No newline at end of file
This diff is collapsed.
from Categorie import Categorie
from Foncteur import Foncteur
from CategorieQuotient import CategorieQuotient
from EnsFinis import EnsFinis, Application
from Morphisme import Morphisme
import itertools
from typing import *
class CategorieComposantesConnexes(CategorieQuotient):
"""`CategorieComposantesConnexes` peut être abrégé en `CatCC`
Catégorie quotientée par la relation d'équivalence sur les objets suivante :
x ~ y si il existe f: x -> y ou il existe f: y -> x."""
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
if nom == None:
nom = "Catégorie des composantes connexes de "+str(categorie_a_quotienter)
CategorieQuotient.__init__(self,categorie_a_quotienter,nom)
for obj1, obj2 in itertools.product(categorie_a_quotienter.objets,repeat=2):
if obj1 != obj2:
if categorie_a_quotienter.existe_morphisme(obj1,obj2) or categorie_a_quotienter.existe_morphisme(obj2,obj1):
self.identifier_morphismes(categorie_a_quotienter.identite(obj1),categorie_a_quotienter.identite(obj2))
for m in categorie_a_quotienter({obj1},{obj2}):
self.identifier_morphismes(m,categorie_a_quotienter.identite(obj2))
for m in categorie_a_quotienter({obj2},{obj1}):
self.identifier_morphismes(m,categorie_a_quotienter.identite(obj2))
else:
for m in categorie_a_quotienter({obj1},{obj1}):
self.identifier_morphismes(m,categorie_a_quotienter.identite(obj1))
CatCC = CategorieComposantesConnexes
def test_CategorieComposantesConnexes():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(3)
for i in range(3):
c = GrapheCompositionAleatoire()
c.transformer_graphviz()
c_a = CategorieComposantesConnexes(c)
c_a.transformer_graphviz()
c_a.fonct_surjection.transformer_graphviz()
for obj in c_a.objets:
c_a.sous_cat_equiv(obj).transformer_graphviz()
class CategorieCatComposantesConnexes(Categorie):
"""Catégorie des catégories composantes connexes.
Les foncteurs sont simplement des applications d'objets."""
def __init__(self,objets:set=set(),nom:str="CatCatComposantesConnexes"):
Categorie.__init__(self,objets,nom)
def __call__(self, sources:set, cibles:set) -> Generator[Application,None,None]:
'''/!\ __call__ n'appelle pas __getitem__ /!\ '''
ens_finis = EnsFinis()
for source in sources:
for cible in cibles:
ens_finis |= {source.objets,cible.objets}
for m in ens_finis.__call__({source.objets},{cible.objets}):
m = m.as_dict()
yield Foncteur(source,cible,m,{source.identite(obj):cible.identite(m[obj]) for obj in m})
def __getitem__(self, couple_sources_cibles:tuple) -> Generator[Application,None,None]:
'''/!\ __call__ n'appelle pas __getitem__ /!\ '''
sources,cibles = couple_sources_cibles
ens_finis = EnsFinis()
for source in sources:
for cible in cibles:
ens_finis |= {source.objets,cible.objets}
for m in ens_finis.__getitem__(({source.objets},{cible.objets})):
m = m.as_dict()
yield Foncteur(source,cible,m,{source.identite(obj):cible.identite(m[obj]) for obj in m})
def test_CategorieCatComposantesConnexes():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(3)
C = CategorieCatComposantesConnexes()
c = GrapheCompositionAleatoire()
c.transformer_graphviz()
c_a = CategorieComposantesConnexes(c)
c_a.transformer_graphviz()
C |= {c_a}
c = GrapheCompositionAleatoire()
c.transformer_graphviz()
c_a2 = CategorieComposantesConnexes(c)
c_a2.transformer_graphviz()
C |= {c_a2}
print(len(list(C({c_a},{c_a2}))))
print(len(list(C({c_a2},{c_a}))))
C.transformer_graphviz()
if __name__ == '__main__':
test_CategorieComposantesConnexes()
# test_CategorieCatComposantesConnexes()
\ No newline at end of file
from Categorie import Categorie
from CategorieQuotient import CategorieQuotient
class CategorieFactorisee(Categorie):
"""Classe abstraite.
Un catégorie factorisée est une catégorie de catégories tel que le calcul des foncteurs
entre deux catégories est factorisée en passant par une catégorie quotient.
Pour calculer les foncteurs entre C et C', on calcule d'abord les foncteurs F entre
C/R et C'/R puis on calcule tous les foncteurs F_i entre les sous-catégories classes d'équivalence de C et C' que l'on note
C[i] et C'[i] tel que F(C[i]) = C'[i].
On fait ensuite le produit cartésien entre tous ces F_i pour chaque F. On a alors tous les foncteurs de C à C'.
Une catégorie de catégories quotients pour calculer les foncteurs entre catégories quotientées,
une catégorie de catégories classes d'équivalence pour calculer les foncteurs entre classes d'équivalence."""
\ No newline at end of file
from Categorie import Categorie
from CategorieQuotient import CategorieQuotient
import itertools
from typing import *
class CategorieFine(CategorieQuotient):
"""`CategorieFine` peut être abrégé en CatFine
`CategorieFine` est similaire à `CategoriePreordre`, elle passe par une catégorie quotient contrairement à cette dernière.
Catégorie quotientée par la relation d'équivalence sur les morphismes suivante :
f : A->B ~ g : C->D ssi A = C et B = D"""
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
if nom == None:
nom = "Catégorie fine engendrée par "+str(categorie_a_quotienter)
CategorieQuotient.__init__(self,categorie_a_quotienter,nom)
for obj1, obj2 in itertools.product(categorie_a_quotienter.objets,repeat=2):
self.identifier_ensemble_morphismes(set(categorie_a_quotienter({obj1},{obj2})))
CatFine = CategorieFine
def test_CategorieFine():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(3)
for i in range(3):
c = GrapheCompositionAleatoire()
c.transformer_graphviz()
c_a = CategorieFine(c)
c_a.transformer_graphviz()
c_a.fonct_surjection.transformer_graphviz()
if __name__ == '__main__':
test_CategorieFine()
\ No newline at end of file
......@@ -35,9 +35,6 @@ class CategorieLibre(Categorie):
"""Soit C est une catégorie:
C({a_i},{b_i}) renvoie l'ensemble des flèches d'un élément de {a_i} vers un élément de {b_i}.
Pour la catégorie libre, on doit énumérer tous les chemins et les composer.
Les classes filles peuvent surcharger cette méthode pour optimiser les calculs. Il faut alors s'assurer que les morphismes
générés sont les mêmes.
"""
for source in sources:
for cible in cibles:
......@@ -46,22 +43,20 @@ class CategorieLibre(Categorie):
def decomposition_morphisme(self, morphisme:Morphisme) -> Generator[Morphisme,None,None]:
'''Renvoie un générateur de morphismes élémentaires qui composés donnent le `morphisme`.
Les classes filles peuvent surcharger cette méthode pour optimiser les calculs.'''
Les renvoie du dernier au premier.'''
if morphisme in self[{morphisme.source},{morphisme.cible}]:
yield morphisme
else:
def enumerer_chemin_elem_sans_cycle(source:Any, cible:Any, noeuds_deja_visites:tuple=tuple()) -> frozenset():
def enumerer_chemin_elem_sans_cycle(source:Any, cible:Any, morph_deja_visites:frozenset=frozenset()) -> frozenset:
if source == cible:
return frozenset({(self.identite(source),)})
if source not in noeuds_deja_visites:
noeuds_deja_visites = noeuds_deja_visites + (source,)
composees_resultat = frozenset()
for morph in self[{source},self.objets]:
for composition_candidate in enumerer_chemin_elem_sans_cycle(morph.cible, cible, noeuds_deja_visites):
composees_resultat = frozenset()
for morph in self[{source},self.objets]:
if morph not in morph_deja_visites:
for composition_candidate in enumerer_chemin_elem_sans_cycle(morph.cible, cible, morph_deja_visites|{morph}):
composees_resultat |= {composition_candidate+(morph,)}
return composees_resultat
return frozenset()
return composees_resultat
for chemin in enumerer_chemin_elem_sans_cycle(morphisme.source,morphisme.cible):
resultat = functools.reduce(lambda x,y:x@y,chemin)
if resultat == morphisme:
......@@ -71,18 +66,6 @@ class CategorieLibre(Categorie):
def verifier_coherence(self):
"""Vérifie la cohérence de la structure du graphe de composition sous-jacent."""
# on vérifie que tous les morphismes de la catégorie sont dans les morph_entrants et morph_sortants
Categorie.verifier_coherence(self)
for morph in self(self.objets,self.objets):
if len(morph) == 1:
if morph not in self[{morph.source},self.objets]:
raise Exception("Incoherence CategorieLibre : le morphisme "+str(morph)+" ne sort pas de "+str(morph.source))
if morph not in self[self.objets,{morph.cible}]:
raise Exception("Incoherence CategorieLibre : le morphisme "+str(morph)+" n'entre pas dans "+str(morph.cible))
def enumerer_composees_sans_cycle(self, source:Any, cible:Any) -> Generator[Morphisme,None,None]:
"""
Génère tous les morphismes composés allant de `source` à `cible` ne contenant aucun cycle (on ne passe jamais deux fois par le même noeud).
......
from Categorie import Categorie
from CategorieAcyclique import CatAcyclique
from CategorieQuotient import CategorieQuotient
from Morphisme import Morphisme
import itertools
class CategorieOrdre(CatAcyclique):
"""`CategorieOrdre` peut être abrégé en `CatOrdre`.
Catégorie acyclique telle qu'il ne peut pas y avoir plus d'une flèche entre deux objets."""
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
if nom == None:
nom = "Catégorie ordre engendrée par "+str(categorie_a_quotienter)
CatAcyclique.__init__(self,categorie_a_quotienter,nom)
for obj1, obj2 in itertools.product(self.objets,repeat=2):
self.identifier_ensemble_morphismes(set(categorie_a_quotienter(obj1,obj2)))
CatOrdre = CategorieOrdre
def test_CategorieOrdre():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(1)
for i in range(1):
c = GrapheCompositionAleatoire(20,150)
c.transformer_graphviz()
c_a = CatOrdre(c)
c_a.transformer_graphviz()
c_a.fonct_surjection.transformer_graphviz()
for obj in c_a.objets:
c_a.sous_cat_equiv(obj).transformer_graphviz()
print(c_a.tri_topologique())
for morph in c_a(c_a.objets,c_a.objets):
morph.sous_cat_equiv().transformer_graphviz()
if __name__ == '__main__':
test_CategorieOrdre()
\ No newline at end of file
from Categorie import Categorie
from CategorieOrdre import CatOrdre
from CategorieQuotient import CategorieQuotient
import copy
import itertools
from typing import *
class CategorieOrdreTotal(CatOrdre):
"""`CategorieOrdreTotal` peut être abrégé en `CatOrdreTotal`
Catégorie qui correspond à une relation d'ordre totale : catégorie acyclique telle que tous les éléments sont comparables (toutes les flèches sont composables).
De plus, au sein d'un objet classe d'équivalence, il n'y a aucune flèche d'un objet vers un autre.
On fait un tri topologique. En suivant le tri, les objets sont équivalents tant qu'il n'y a aucune flèche intraclasse.
"""
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
if nom == None:
nom = "Catégorie ordre total engendrée par "+str(categorie_a_quotienter)
CatOrdre.__init__(self,categorie_a_quotienter,nom)
tri = self.tri_topologique()
classe_equiv_en_cours = set()
for obj in tri:
if next(categorie_a_quotienter(classe_equiv_en_cours,obj),None) == None:
classe_equiv_en_cours |= obj
else:
self.identifier_ensemble_morphismes(set(map(lambda x:categorie_a_quotienter.identite(x),classe_equiv_en_cours)))
classe_equiv_en_cours = obj
self.identifier_ensemble_morphismes(set(map(lambda x:categorie_a_quotienter.identite(x),classe_equiv_en_cours)))
for classe1,classe2 in itertools.product(self.objets,repeat=2):
self.identifier_ensemble_morphismes(set(categorie_a_quotienter(classe1,classe2)))
CatOrdreTotal = CategorieOrdreTotal
def test_CatOrdreTotal():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(3)
for i in range(10):
c = GrapheCompositionAleatoire()
c.transformer_graphviz()
c_ot = CatOrdreTotal(c)
c_ot.transformer_graphviz()
if __name__ == '__main__':
test_CatOrdreTotal()
\ No newline at end of file
from Categorie import Categorie
from Foncteur import Foncteur
from Morphisme import Morphisme
from typing import *