Commit 466e2d37 authored by Guillaume Sabbagh's avatar Guillaume Sabbagh
Browse files

Catégorie quotient corrigée, on passe par des identification de morphismes,...

Catégorie quotient corrigée, on passe par des identification de morphismes, catégorie acyclique changée en conséquence et catégorie préordre remise comme avant car plus optimisé
parent 49873442
from Categorie import Categorie
from CategorieQuotient import CategorieQuotient
from Morphisme import Morphisme
import itertools
class CategorieAcyclique(CategorieQuotient):
"""Catégorie quotientée par la relation d'équivalence sur les objets suivante :
......@@ -7,31 +9,26 @@ class CategorieAcyclique(CategorieQuotient):
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
if nom == None:
nom = "Catégorie acyclique engendrée par "+str(categorie_a_quotienter)
obj_vers_classe = dict()
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 rel_equiv_morph(self, morph1:Morphisme, morph2:Morphisme) -> bool:
if morph1 == morph2:
return True
if morph1.is_identite and morph2.is_identite:
obj1,obj2 = morph1.source,morph2.source
return self.cat_a_quotienter.existe_morphisme(obj1,obj2) and self.cat_a_quotienter.existe_morphisme(obj2,obj1)
else:
obj1,obj2 = morph1.source,morph2.source
obj3,obj4 = morph1.cible,morph2.cible
return self.rel_equiv_morph(self.cat_a_quotienter.identite(obj1),self.cat_a_quotienter.identite(obj2)) and \
self.rel_equiv_morph(self.cat_a_quotienter.identite(obj3),self.cat_a_quotienter.identite(obj4)) and \
self.rel_equiv_morph(self.cat_a_quotienter.identite(obj1),self.cat_a_quotienter.identite(obj3))
def test_CategorieAcyclique():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(1233)
random.seed(3)
for i in range(5):
c = GrapheCompositionAleatoire()
c.transformer_graphviz()
......
......@@ -144,7 +144,7 @@ class GrapheCompositionAleatoire(GC):
if nouvelle_loi.table[(f,g)] == None:
# problème de composition
for k in range(nb_tentatives):
nouvelle_loi2 = tentative_complexification(nouvelle_loi,f,g,nb_tentatives) #on tente de complexifier
nouvelle_loi2 = tentative_complexification(nouvelle_loi,f,g,nb_tentatives-int(not random.randint(0,2))) #on tente de complexifier
if nouvelle_loi2 != None:
nouvelle_loi = nouvelle_loi2
break
......
from CategorieQuotient import CategorieQuotient
from Categorie import Categorie
from Foncteur import Foncteur
from Morphisme import Morphisme
from typing import *
class MorphismePreordre(Morphisme):
"""MorphismePreordre peut être abrégé en MPO.
Un morphisme préordre est un morphisme au sein d'une catégorie préordre,
c'est-à-dire qu'entre deux objets il y a au plus un morphisme.
Deux morphismes préordre sont égaux ssi ils ont la même source et la même cible."""
def __init__(self, source:Any, cible:Any, nom:str = None):
Morphisme.__init__(self,source,cible,nom,source==cible)
def __eq__(self, other:'MorphismePreordre') -> bool:
return type(self) == type(other) and self.source == other.source and self.cible == other.cible
def __hash__(self) -> int:
return hash((self.source,self.cible))
def __matmul__(self, other:'MorphismePreordre') -> 'MorphismePreordre':
return MorphismePreordre(other.source,self.cible,str(self)+'o'+str(other))
MPO = MorphismePreordre
class CategoriePreordre(Categorie):
"""CategoriePreordre peut être abrégé en CatFine.
Classe abstraite qui définit ce qu'est une catégorie préordre.
Les classes filles doivent implémenter la méthode existe_morphisme(self,source:Any,cible:Any) -> Union(Morphisme,None) qui détermine
entièrement les flèches de la catégorie.
Une catégorie préordre C est une catégorie telle qu'il n'y a pas plus de deux flèches entre deux objets de C."""
_id = 0
def __init__(self, objets:set = set(), nom:Union[str,None] = None):
CategoriePreordre._id += 1
self._id = CategoriePreordre._id
if nom == None:
nom = "Catégorie préordre "+str(self._id)
self.nom = nom
Categorie.__init__(self, objets, nom)
def identite(self, objet:Any) -> MorphismePreordre:
return MorphismePreordre(objet,objet)
def __call__(self, sources:set, cibles:set) -> Generator[MorphismePreordre,None,None]:
for source in sources:
for cible in cibles:
if self.existe_morphisme(source,cible) != None:
yield MorphismePreordre(source,cible)
def __getitem__(self, couple_sources_cibles:tuple) -> Generator[MorphismePreordre,None,None]:
sources,cibles = couple_sources_cibles
for source in sources:
for cible in cibles:
if self.existe_morphisme_elementaire(source,cible) != None:
yield MorphismePreordre(source,cible)
CatFine = CategoriePreordre
class CategoriePreordreEngendree(CategoriePreordre):
"""La catégorie préordre engendrée par une catégorie C est une sous-catégorie maximale C' de C
telle qu'il n'y a pas plus de deux flèches entre deux objets de C'.
L'attribut `foncteur_engendre` est le foncteur de la catégorie initiale vers la catégorie préordre engendrée."""
def __init__(self, categorie:Categorie, nom:str = None):
self._categorie_initiale = categorie
CategoriePreordre.__init__(self,categorie.objets, "Catégorie préordre engendrée par "+str(categorie) if nom == None else nom)
def existe_morphisme(self, source:Any, cible:Any) -> Union[Morphisme,None]:
return self._categorie_initiale.existe_morphisme(source,cible)
def existe_morphisme_elementaire(self, source:Any, cible:Any) -> Union[Morphisme,None]:
return self._categorie_initiale.existe_morphisme_elementaire(source,cible)
@property
def foncteur_engendre(self):
c_i = self._categorie_initiale
return Foncteur(c_i,self,{o:o for o in self.objets},{m:next(self({m.source},{m.cible})) for m in c_i[c_i.objets,c_i.objets]})
def test_CategoriePreordreEngendree():
from GrapheDeComposition import GC,MGC
gc = GC()
gc |= set("ABCDEF")
f,g,h,i,j,k,l = [MGC('A','B','f'),MGC('B','C','g'),MGC('D','E','h'),MGC('E','F','i'),MGC('A','D','j'),MGC('B','E','k'),MGC('C','F','l')]
gc |= {f,g,h,i,j,k,l}
gc.transformer_graphviz()
c_po = CategoriePreordreEngendree(gc)
c_po.transformer_graphviz()
c_po.foncteur_engendre.transformer_graphviz()
if __name__ == '__main__':
test_CategoriePreordreEngendree()
'''from CategorieQuotient import CategorieQuotient
from Morphisme import Morphisme
from typing import *
......@@ -40,4 +139,4 @@ def test_CategoriePreordre():
if __name__ == '__main__':
test_CategoriePreordre()
\ No newline at end of file
test_CategoriePreordre()'''
\ No newline at end of file
......@@ -15,15 +15,21 @@ class ObjetQuotient(frozenset):
class MorphismeQuotient(Morphisme):
"""Morphisme d'une catégorie après le passage au quotient.
Un `MorphismeQuotient` est donc une classe d'équivalence de morphismes de la catégorie initiale.
On peut itérer sur le morphisme pour obtenir tous les morphismes initiaux qui ont généré le `MorphismeQuotient`."""
On peut itérer sur le morphisme pour obtenir tous les morphismes initiaux qui ont généré le `MorphismeQuotient`.
On peut aussi accéder en lecture à classe_morphismes."""
def __init__(self, source:ObjetQuotient, cible:ObjetQuotient, morphismes:Set[Morphisme], nom:str = None):
def __init__(self, source:ObjetQuotient, cible:ObjetQuotient, morphismes:Set[Morphisme], cat_quotient_associee:'CategorieQuotient', nom:str = None):
Morphisme.__init__(self,source,cible,nom,any(map(lambda x:x.is_identite,morphismes)))
self._classe_morphismes = frozenset(morphismes)
self._cat_quotient_associee = cat_quotient_associee
def __eq__(self, other:'MorphismeQuotient') -> bool:
return issubclass(type(other),MorphismeQuotient) and self._classe_morphismes == other._classe_morphismes
@property
def classe_morphismes(self) -> frozenset:
return self._classe_morphismes
def __eq__(self, other:'MorphismeQuotient') -> bool:
return issubclass(type(other),MorphismeQuotient) and self.classe_morphismes == other.classe_morphismes
def __hash__(self) -> int:
return hash((self.source,self.cible))
......@@ -32,80 +38,105 @@ class MorphismeQuotient(Morphisme):
yield m
def __str__(self) -> str:
return '{'+','.join(map(str,self._classe_morphismes))+'}'
def __repr__(self) -> str:
return '{'+','.join(map(str,self._classe_morphismes))+'}'
def __matmul__(self,other:'MorphismeQuotient') -> 'MorphismeQuotient':
return MorphismeQuotient(other.source,self.cible,{m2@m1 for m1 in other for m2 in self if m1.cible == m2.source})
for m1 in other:
for m2 in self:
if m1.cible == m2.source:
return self._cat_quotient_associee.fonct_surjection(m2@m1)
if config.WARNING_CATEGORIE_QUOTIENT:
print("Warning : lors du calcul de la composition "+str(self)+" o "+str(other)+", aucun morphisme composable")
return MorphismeQuotient(other.source,self.cible,set(),self)
class CategorieQuotient(CategorieLibre):
"""Classe abstraite.
Définit ce qu'est une catégorie quotient.
Les classes filles doivent surcharger une méthode :
- rel_equiv_morph : fonction qui définit la relation d'équivalence sur les morphismes
(les identités équivalentes définissent la relation d'équivalence sur les objets).
"""
Définit ce qu'est une catégorie quotient. Cette classe est lourde et ne passe pas à l'échelle sur de grosses catégories.
Elle donne accès au foncteur de sujection canonique qui est un foncteur qui nécessite de spécifier les applications d'objets et de
morphismes complètement (et pas seulement des morphismes élémentaires).
Si on n'a pas besoin de ce foncteur de sujection canonique, il est recommandé de faire sa propre classe qui définit
les méthodes __getitem__ et __call__ pour qu'elles soient plus efficaces que par la catégorie quotient qui a une granularité plus fine.
La principale utilité des catégories quotients est l'énumération des foncteurs entre deux catégories en factorisant
par une catégorie quotient qui reflète une propriété que le foncteur devra vérifier.
On peut définir la relation d'équivalence en appelant la méthode identifier_morphismes.
Cette classe donne accès en lecture au foncteur de surjection canonique : fonct_surjection.
Cette classe donne accès en lecture à la catégorie à quotienter : cat_a_quotienter.
Pour voir un exemple, voir `CategoriePreordre` et `CategorieAcyclique`."""
Pour voir un exemple, voir `CategorieAcyclique` et `CategorieComposantesConnexes`."""
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
c = categorie_a_quotienter
self._categorie_a_quotienter = c
obj_vers_classe = dict() # associe à chaque objet son `ObjetQuotient` associé
classe_vers_obj = dict()
morph_vers_classe = dict() # associe à chaque morphisme sa classe d'équivalence
classe_vers_morph = dict()
for obj in c.objets:
for cle in obj_vers_classe:
if self.rel_equiv_morph(c.identite(cle),c.identite(obj)):
obj_vers_classe[obj] = obj_vers_classe[cle]
classe_vers_obj[obj_vers_classe[obj]] |= {obj}
break
else:
obj_vers_classe[obj] = len(set(obj_vers_classe.values()))+1
classe_vers_obj[obj_vers_classe[obj]] = {obj}
obj_vers_classe = {o:ObjetQuotient(classe_vers_obj[obj_vers_classe[o]]) for o in c.objets}
for morph in c(c.objets,c.objets):
for cle in morph_vers_classe:
if self.rel_equiv_morph(morph,cle):
morph_vers_classe[morph] = morph_vers_classe[cle]
classe_vers_morph[morph_vers_classe[morph]] |= {morph}
break
else:
morph_vers_classe[morph] = len(set(morph_vers_classe.values()))+1
classe_vers_morph[morph_vers_classe[morph]] = {morph}
classe_vers_morph_quotient = {classe:MorphismeQuotient(obj_vers_classe[list(classe_vers_morph[classe])[0].source],\
obj_vers_classe[list(classe_vers_morph[classe])[0].cible],classe_vers_morph[classe]) for classe in classe_vers_morph}
morph_vers_classe = {m:classe_vers_morph_quotient[morph_vers_classe[m]] for m in c(c.objets,c.objets)}
CategorieLibre.__init__(self,set(obj_vers_classe.values()),nom if nom != None else "Catégorie quotient de "+str(categorie_a_quotienter))
backup = config.TOUJOURS_VERIFIER_COHERENCE
config.TOUJOURS_VERIFIER_COHERENCE = False
self._fonct_surjection = Foncteur(c,self,obj_vers_classe,morph_vers_classe)
config.TOUJOURS_VERIFIER_COHERENCE = backup
if backup:
self._fonct_surjection.verifier_coherence()
if config.TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
self._obj_vers_classe = {o:ObjetQuotient({o}) for o in c.objets} # associe à chaque objet son `ObjetQuotient` associé
self._morph_vers_classe = {m:MorphismeQuotient(self._obj_vers_classe[m.source],\
self._obj_vers_classe[m.cible],{m},self) for m in c(c.objets,c.objets)} # associe à chaque morphisme son `MorphismeQuotient` associé
CategorieLibre.__init__(self,set(self._obj_vers_classe.values()),nom if nom != None else "Catégorie quotient de "+str(categorie_a_quotienter))
def verifier_coherence(self):
CategorieLibre.verifier_coherence(self)
# on vérifie que la relation d'équivalence en est une (réflexive, symétrique, transitive)
for f,g,h in itertools.product(self.cat_a_quotienter[self.cat_a_quotienter.objets,self.cat_a_quotienter.objets],repeat=3):
if not self.rel_equiv_morph(f,f):
raise Exception("Incoherence CategorieQuotient : la relation d'equivalence n'est pas reflexive "+str(f))
if self.rel_equiv_morph(f,g) and not self.rel_equiv_morph(g,f):
raise Exception("Incoherence CategorieQuotient : la relation d'equivalence n'est pas symetrique "+str(f)+" "+str(g))
if self.rel_equiv_morph(f,g) and self.rel_equiv_morph(g,h) and not self.rel_equiv_morph(f,h):
raise Exception("Incoherence CategorieQuotient : la relation d'equivalence n'est pas transitive "+str(f)+" "+str(g)+" "+str(h))
def identifier_morphismes(self, morph1:Morphisme, morph2:Morphisme):
"""Identifie le `morph1` au `morph2`.
La relation d'équivalence force la réflexivité, la symétrie et la transitivité.
La relation d'équivalence doit aussi être compatible avec la composition.
Ces contraintes sont automatiquement vérifiées par construction."""
# on ne vérifie pas que si f~h et g~i alors fog ~ hoi
# cette vérification est déjà fait lors de la construction du foncteur
if self._morph_vers_classe[morph1] != self._morph_vers_classe[morph2]:
if morph2.source not in self._obj_vers_classe[morph1.source]:
# Il faut identifier les deux sources
obj_a_update = self._obj_vers_classe[morph1.source]|self._obj_vers_classe[morph2.source]
self -= {self._obj_vers_classe[morph1.source],self._obj_vers_classe[morph2.source]}
self |= {obj_a_update}
for obj in obj_a_update:
self._obj_vers_classe[obj] = ObjetQuotient(obj_a_update)
for obj in obj_a_update:
self.identifier_morphismes(self.cat_a_quotienter.identite(morph1.source),self.cat_a_quotienter.identite(obj))
for morph in self._morph_vers_classe:
m = self._morph_vers_classe[morph]
if len(m.source-obj_a_update) == 0: #source inclus dans les obj_a_update
self._morph_vers_classe[morph] = MorphismeQuotient(obj_a_update,m.cible,m.classe_morphismes,self)
for morph in self._morph_vers_classe:
m = self._morph_vers_classe[morph]
if len(m.cible-obj_a_update) == 0: #cible inclus dans les obj_a_update
self._morph_vers_classe[morph] = MorphismeQuotient(m.source,obj_a_update,m.classe_morphismes,self)
if morph2.cible not in self._obj_vers_classe[morph1.cible]:
# Il faut identifier les deux sources
obj_a_update = self._obj_vers_classe[morph1.cible]|self._obj_vers_classe[morph2.cible]
self -= {self._obj_vers_classe[morph1.cible],self._obj_vers_classe[morph2.cible]}
self |= {obj_a_update}
for obj in obj_a_update:
self._obj_vers_classe[obj] = ObjetQuotient(obj_a_update)
for obj in obj_a_update:
self.identifier_morphismes(self.cat_a_quotienter.identite(morph1.cible),self.cat_a_quotienter.identite(obj))
for morph in self._morph_vers_classe:
m = self._morph_vers_classe[morph]
if len(m.source-obj_a_update) == 0: #source inclus dans les obj_a_update
self._morph_vers_classe[morph] = MorphismeQuotient(obj_a_update,m.cible,m.classe_morphismes,self)
for morph in self._morph_vers_classe:
m = self._morph_vers_classe[morph]
if len(m.cible-obj_a_update) == 0: #cible inclus dans les obj_a_update
self._morph_vers_classe[morph] = MorphismeQuotient(m.source,obj_a_update,m.classe_morphismes,self)
if self._morph_vers_classe[morph1] != self._morph_vers_classe[morph2]:
morph_a_update = self._morph_vers_classe[morph1].classe_morphismes|self._morph_vers_classe[morph2].classe_morphismes
nouveau_morph = MorphismeQuotient(self._obj_vers_classe[morph1.source],self._obj_vers_classe[morph1.cible],morph_a_update,self)
for morph in morph_a_update:
self._morph_vers_classe[morph] = nouveau_morph
for m1 in self.cat_a_quotienter(self.cat_a_quotienter.objets,self.cat_a_quotienter.objets):
for m2 in self.cat_a_quotienter({m1.cible},self.cat_a_quotienter.objets):
for m3 in self._morph_vers_classe[m1]:
for m4 in self._morph_vers_classe[m2]:
if m3.cible == m4.source:
if self._morph_vers_classe[m2@m1] != self._morph_vers_classe[m4@m3]:
self.identifier_morphismes(m2@m1,m4@m3)
@property
def fonct_surjection(self) -> Foncteur:
'''Renvoie le foncteur de sujection canonique.'''
return self._fonct_surjection
return Foncteur(self.cat_a_quotienter,self,self._obj_vers_classe,self._morph_vers_classe)
@property
def cat_a_quotienter(self) -> Categorie:
......@@ -118,17 +149,34 @@ class CategorieQuotient(CategorieLibre):
for source in sources:
for cible in cibles:
for fleche_elem in self.cat_a_quotienter[source,cible]:
yield self.fonct_surjection(fleche_elem)
yield self._morph_vers_classe[fleche_elem]
def identite(self, objet:ObjetQuotient) -> MorphismeQuotient:
return self.fonct_surjection(self.cat_a_quotienter.identite(next(iter(objet))))
def rel_equiv_morph(self, morph1:Morphisme, morph2:Morphisme) -> bool:
'''Cette méthode doit être une relation d'équivalence sur toute les flèches de la catégorie à quotienter.'''
raise NotImplementedError("Les classes filles doivent implementer cette methode afin de definir la relation d'equivalence par laquelle quotienter.")
def test_CategorieQuotient():
from CategorieAleatoire import GrapheCompositionAleatoire
import random
random.seed(13)
c = CategorieQuotient(GrapheCompositionAleatoire())
c.transformer_graphviz()
for i in range(3):
print("liste des morphismes : ",list(c(c.objets,c.objets)))
morph1,morph2 = random.sample([m for m in c(c.objets,c.objets) if len(list(iter(m))) > 0],2)
morph1 = random.choice(list(morph1.classe_morphismes))
morph2 = random.choice(list(morph2.classe_morphismes))
print("Identification de deux morphismes :")
print(morph1)
print(morph2)
c.identifier_morphismes(morph1,morph2)
c.transformer_graphviz()
c.fonct_surjection.transformer_graphviz()
print()
if __name__ == '__main__':
test_CategorieQuotient()
......@@ -4,6 +4,7 @@ DEBUG_LOI_DE_COMPOSITION = False
GRAPHVIZ_ENABLED = True # booléen qui indique s'il faut charger la bibliothèque graphviz
GRAPHVIZ_CONCENTRATE_GRAPHS = False
WARNING_LIMITE_FLECHES_ATTEINTE = True
WARNING_CATEGORIE_QUOTIENT = True
PRINT_AVANCEMENT_CREATION_CAT_ALEA = True
CLEAN_GRAPHVIZ_MODEL = True
PROGRESS_BAR = True
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment