Commit 4a7101c7 authored by Guillaume Sabbagh's avatar Guillaume Sabbagh
Browse files

Merge

parents a9242fcf eaa1a2c2
......@@ -216,6 +216,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]:
......
This diff is collapsed.
......@@ -46,17 +46,16 @@ class CategorieLibre(Categorie):
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:y@x,chemin)
if resultat == morphisme:
......
from Categorie import Categorie
from Foncteur import Foncteur
from Morphisme import Morphisme
from typing import *
class MorphismePreordre(Morphisme):
"""Un morphisme préordre est un morphisme au sein d'une catégorie préordre,
"""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."""
......@@ -19,14 +21,24 @@ class MorphismePreordre(Morphisme):
def __matmul__(self, other:'MorphismePreordre') -> 'MorphismePreordre':
return MorphismePreordre(other.source,self.cible,str(self)+'o'+str(other))
MPO = MorphismePreordre
class CategoriePreordre(Categorie):
"""Classe abstraite qui définit ce qu'est une catégorie préordre.
"""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."""
def __init__(self, objets:set = set(), nom:str = "Catégorie préordre"):
Categorie.__init__(self,objets,nom)
_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)
......@@ -36,11 +48,21 @@ class CategoriePreordre(Categorie):
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'."""
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
......@@ -49,7 +71,15 @@ class CategoriePreordreEngendree(CategoriePreordre):
def existe_morphisme(self, source:Any, cible:Any) -> Union[Morphisme,None]:
return self._categorie_initiale.existe_morphisme(source,cible)
def test_CategoriePreordre():
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()
......@@ -62,5 +92,7 @@ def test_CategoriePreordre():
c_po = CategoriePreordreEngendree(gc)
c_po.transformer_graphviz()
c_po.foncteur_engendre.transformer_graphviz()
if __name__ == '__main__':
test_CategoriePreordre()
\ No newline at end of file
test_CategoriePreordreEngendree()
\ No newline at end of file
from Categorie import Categorie
from Morphisme import Morphisme
from typing import *
class ObjetQuotient(set):
"""Objet d'une catégorie après le passage au quotient.
Un `ObjetQuotient` est donc une classe d'équivalence d'objets de la catégorie initiale."""
pass
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`."""
def __init__(self, source:ObjetQuotient, cible:ObjetQuotient, morphismes:Set[Morphisme], nom:str = None):
Morphisme.__init__(self,source,cible,nom,any(map(lambda x:x.id_identite,morphismes)))
self._classe_morphismes = morphismes
def __iter__(self) -> Generator[Morphisme,None,None]:
for m in self._classe_morphismes:
yield m
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})
class CategorieQuotient(CategorieLibre):
"""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)."""
def __init__(self,categorie_a_quotienter:Categorie, nom:str = None):
Categorie.__init__(nom if nom != None else "Catégorie quotient de "+str(categorie_a_quotienter))
c = categorie_a_quotienter
self.categorie_a_quotienter = c
self._obj_vers_classe = dict() # associe à chaque objet son `ObjetQuotient` associé
morph_vers_classe = dict() # associe à chaque morphisme sa classe d'équivalence
classe_vers_morph = dict()
for obj in c.objets:
for cle in self._obj_vers_classe:
if self.rel_equiv_morph(c.identite(cle),c.identite(obj)):
self._obj_vers_classe[cle] |= {obj}
self._obj_vers_classe[obj] = self._obj_vers_classe[cle]
break
else:
self._obj_vers_classe[obj] = ObjetQuotient({obj})
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[cle]] |= {morph}
break
else:
morph_vers_classe[morph] = len(list(morph_vers_classe.values()))+1
classe_vers_morph[morph_vers_classe[morph]] = {morph}
classe_vers_morph_quotient = {classe:MorphismeQuotient(self._obj_vers_classe[classe_vers_morph[classe][0].source],\
self._obj_vers_classe[classe_vers_morph[classe][0].cible],classe_vers_morph[classe]) for classe in classe_vers_morph}
self._morph_vers_classe = {m:classe_vers_morph_quotient[morph_vers_classe[m]] for m in c[c.objets,c.objets]}
self |= set(self._obj_vers_classe.values())
def __getitem__(self,couple_sources_cibles:tuple) -> Generator[MorphismeQuotient,None,None]:
sources,cibles = couple_sources_cibles
for source in sources:
for cible in cibles:
\ No newline at end of file
from Categorie import Categorie
from Foncteur import Foncteur
from CategoriePreordre import CatFine,MPO
from Diagramme import DiagrammeIdentite
from config import *
from typing import *
import copy
class CategorieSquelettique(CatFine):
"""
Classe abstraite qui définit ce qu'est une catégorie squelettique.
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 squelettique C est une catégorie telle qu'il n'y a pas plus de deux flèches entre deux objets de C et telle qu'il n'y a aucun cycle autres que les identités.
https://ncatlab.org/nlab/show/skeleton"""
_id = 0
def __init__(self, objets:set = set(), nom:Union[str,None] = None):
CategorieSquelettique._id += 1
self._id = CategorieSquelettique._id
if nom == None:
nom = "Catégorie squelettique "+str(self._id)
self.nom = nom
CatFine.__init__(self,objets,nom)
if TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
def tri_topologique(self) -> list:
"""Renvoie un tri topologique de la catégorie, c'est-à-dire une liste d'objets
tels qu'un objet en précède un autre dans la liste uniquement s'il n'y a pas de morphisme du deuxième vers le premier.
Renvoie une liste vide si la catégorie n'a pas d'objet."""
if len(self.objets) > 0:
copie = copy.copy(self)
result = []
while len(copie.objets) > 0:
objets_sans_predecesseurs = [obj for obj in copie.objets if len({e for e in set(copie(copie.objets,{obj})) if not e.is_identite}) == 0]
if len(objets_sans_predecesseurs) == 0:
raise Exception("Incoherence CategorieSquelettique : le graphe sous-jacent n'est pas acyclique "+str(copie.objets))
result += objets_sans_predecesseurs
copie -= set(objets_sans_predecesseurs)
return result
return []
def verifier_coherence(self):
CatFine.verifier_coherence(self)
self.tri_topologique()
class CategorieSquelettiqueEngendree(CategorieSquelettique):
"""La catégorie squelettique 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'.
Si la catégorie initiale n'a pas un graphe sous-jacent acyclique, on créé un objet par classe d'équivalence ou la relation
d'équivalence est a ~ b ssi il existe un morphisme de a vers b et un morphisme de b vers a.
L'attribut `foncteur_engendre` est le foncteur de la catégorie initiale vers la catégorie squelettique engendrée."""
def __init__(self, categorie:Categorie, nom:str = None):
self._categorie_initiale = categorie
self.classes_equiv_vers_obj = dict() # associe à chaque classe d'équivalence un ensemble d'objets {classe : {obj1,obj2,...}}
self.obj_vers_classes_equiv = dict() # associe à chaque objet sa classe d'équivalence {obj:classe}
for obj1 in categorie.objets:
for obj2 in self.obj_vers_classes_equiv:
if categorie.existe_morphisme(obj1,obj2) and categorie.existe_morphisme(obj2,obj1):
self.classes_equiv_vers_obj[self.obj_vers_classes_equiv[obj2]] |= {obj1}
self.obj_vers_classes_equiv[obj1] = self.obj_vers_classes_equiv[obj2]
break
else:
self.obj_vers_classes_equiv[obj1] = len(self.obj_vers_classes_equiv)+1
self.classes_equiv_vers_obj[self.obj_vers_classes_equiv[obj1]] = {obj1}
CategorieSquelettique.__init__(self,set(self.classes_equiv_vers_obj.keys()), "Catégorie squelettique engendrée par "+str(categorie) if nom == None else nom)
def existe_morphisme(self, source:Any, cible:Any) -> Union[MPO,None]:
for s in self.classes_equiv_vers_obj[source]:
for c in self.classes_equiv_vers_obj[cible]:
morph = self._categorie_initiale.existe_morphisme(s,c)
if morph != None:
return morph
def existe_morphisme_elementaire(self, source:Any, cible:Any) -> Union[MPO,None]:
for s in self.classes_equiv_vers_obj[source]:
for c in self.classes_equiv_vers_obj[cible]:
morph = self._categorie_initiale.existe_morphisme_elementaire(s,c)
if morph != None:
return morph
@property
def foncteur_engendre(self):
c_i = self._categorie_initiale
return Foncteur(c_i,self,{o:self.obj_vers_classes_equiv[o] for o in c_i.objets},{m:next(self({self.obj_vers_classes_equiv[m.source]},{self.obj_vers_classes_equiv[m.cible]})) for m in c_i[c_i.objets,c_i.objets]})
def test_CategorieSquelettiqueEgendree():
from GrapheDeComposition import GC,MGC
gc = GC()
gc |= set("ABCDEF")
f,g,h,i,j,k,l,m,n = [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('B','A','l'),MGC('B','D','m'),MGC('C','E','n')]
gc |= {f,g,h,i,j,k,l,m,n}
MGC.identifier_morphismes(l@f,gc.identite('A'))
gc.transformer_graphviz()
squelette = CategorieSquelettiqueEngendree(gc)
squelette.transformer_graphviz()
print(squelette.tri_topologique())
squelette.foncteur_engendre.transformer_graphviz()
def test_CategorieSquelettiqueEgendree2():
from CategorieAleatoire import GCA
import random
random.seed(1334442)
for i in range(20):
gc = GCA()
gc.transformer_graphviz()
squelette = CategorieSquelettiqueEngendree(gc)
squelette.transformer_graphviz()
print(squelette.tri_topologique())
squelette.foncteur_engendre.transformer_graphviz()
if __name__ == '__main__':
test_CategorieSquelettiqueEgendree()
test_CategorieSquelettiqueEgendree2()
\ No newline at end of file
......@@ -135,8 +135,9 @@ class Foncteur(Morphisme):
nodes = []
for o in self.cible.objets:
cluster.node(str(o)+suffixe2_objet)
for morph in set(self.cible[self.cible.objets,self.cible.objets])|set(self._app_morph.values()):
if not morph.is_identite or afficher_identites:
fleches2 = set(self.cible[self.cible.objets,self.cible.objets])|set(self._app_morph.values())
for morph in fleches2:
if not morph.is_identite or afficher_identites or morph in self._app_morph.values():
couleur = 'black' if not issubclass(type(morph),MGC) or len(morph) == 1 else 'grey70'
cluster.node(str(morph)+suffixe2_morph,style="invis",shape="point")
graph.edge(str(morph.source)+suffixe2_objet,str(morph)+suffixe2_morph,color=couleur,label=str(morph)+suffixe2_morph,headclip="False",arrowhead="none")
......@@ -153,10 +154,13 @@ class Foncteur(Morphisme):
cluster.node(str(morph)+suffixe2_morph,style="invis",shape="point")
graph.edge(str(morph.source)+suffixe2_objet,str(morph)+suffixe2_morph,color=couleur,label=str(morph)+suffixe2_morph,headclip="False",arrowhead="none")
graph.edge(str(morph)+suffixe2_morph,str(morph.cible)+suffixe2_objet,color=couleur,tailclip="false")
graph.edge(str(source)+suffixe1_morph,str(self(source))+suffixe2_morph,color="cyan")
fleche2 = {f for f in fleches2 if f == self(source)}.pop()
graph.edge(str(source)+suffixe1_morph,str(fleche2)+suffixe2_morph,color="cyan")
for source in self._app_objets:
graph.edge(str(source)+suffixe1_objet,str(self(source))+suffixe2_objet,color="blue")
obj1 = {e for e in self.source.objets if e == source}.pop()
obj2 = {e for e in self.cible.objets if e == self(source)}.pop()
graph.edge(str(obj1)+suffixe1_objet,str(obj2)+suffixe2_objet,color="blue")
graph.render(destination)
if CLEAN_GRAPHVIZ_MODEL:
......
......@@ -59,4 +59,22 @@ utiliser existe_morphisme plutôt que self({cocone_courant},cocones_restants) da
faire une seule classe catégorie homologue en créant une méthode sommet dans cone et cocone;
faire un test avec la catégorie de foncteurs avec pour objets CatégorieBij(1,2,3) et CatégorieBij(a,b,c)
\ No newline at end of file
faire un test avec la catégorie de foncteurs avec pour objets CatégorieBij(1,2,3) et CatégorieBij(a,b,c)
optimiser CatFinies et la recherche de foncteurs entre deux catégories :
- réunir les objets en classes d'équivalence selon la relation o1 ~ o2 ssi il existe f:o1->o2 et g:o2->o1.
- faire un graphe des classes d'équivalence avec comme noeuds les classes d'équivalence. Il y a une flèche
entre deux classes d'équivalence s'il existe au moins une flèche d'un objet d'une classe vers un objet de l'autre classe.
- ce graphe est acyclique.
- un foncteur doit préserver la structure de graphe acyclique -> (faire la catégorie des graphes acycliques ?)
Catégorie aléatoire : laisser la possibilité que la composition donne l'identité.
-1 donne les morphismes eux même lorsque composés (identité)
si source et target dans même classes d'équivalence, alors ajouter l'outcome -1
La catégorie squelettique n'est pas une catégorie squelettique, les cycles ne sont pas forcément des isomorphismes.
Trouver un autre nom pour ces catégories
Faire un argument par défaut pour call et getitem pour qu'on ai besoin de rien mettre pour avoir toutes les flèches
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