Commit adab0db9 authored by Guillaume Sabbagh's avatar Guillaume Sabbagh
Browse files

Catégorie quotient fonctionne, suppression de catégorie squelettique au profit...

Catégorie quotient fonctionne, suppression de catégorie squelettique au profit de catégorie acyclique.
parent 4a7101c7
......@@ -146,7 +146,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
......
from CategorieQuotient import CategorieQuotient
from Morphisme import Morphisme
class CategorieAcyclique(CategorieQuotient):
"""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 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)
for i in range(5):
c = GrapheCompositionAleatoire()
c.transformer_graphviz()
c_a = CategorieAcyclique(c)
c_a.transformer_graphviz()
c_a.fonct_surjection.transformer_graphviz()
if __name__ == '__main__':
test_CategorieAcyclique()
\ No newline at end of file
from GrapheDeComposition import GC,MGC
from Foncteur import Foncteur
from Monoide import Monoide,ElementMonoideGC,MonoideGC
from Categorie import Categorie
from CategorieProduit import CategorieProduit
......@@ -143,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-1) #on tente de complexifier
nouvelle_loi2 = tentative_complexification(nouvelle_loi,f,g,nb_tentatives) #on tente de complexifier
if nouvelle_loi2 != None:
nouvelle_loi = nouvelle_loi2
break
......
......@@ -42,7 +42,8 @@ class CategorieLibre(Categorie):
yield morph
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`.'''
'''Renvoie un générateur de morphismes élémentaires qui composés donnent le `morphisme`.
Les renvoie du dernier au premier.'''
if morphisme in self[{morphisme.source},{morphisme.cible}]:
yield morphisme
else:
......@@ -57,7 +58,7 @@ class CategorieLibre(Categorie):
return composees_resultat
for chemin in enumerer_chemin_elem_sans_cycle(morphisme.source,morphisme.cible):
resultat = functools.reduce(lambda x,y:y@x,chemin)
resultat = functools.reduce(lambda x,y:x@y,chemin)
if resultat == morphisme:
for morph_elem in chemin:
yield morph_elem
......@@ -65,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 Categorie import Categorie, unique_generator
from CategorieLibre import CategorieLibre
from Foncteur import Foncteur
from Morphisme import Morphisme
import itertools
import config
from typing import *
class ObjetQuotient(set):
class ObjetQuotient(frozenset):
"""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
......@@ -14,59 +18,120 @@ class MorphismeQuotient(Morphisme):
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
Morphisme.__init__(self,source,cible,nom,any(map(lambda x:x.is_identite,morphismes)))
self._classe_morphismes = frozenset(morphismes)
def __eq__(self, other:'MorphismeQuotient') -> bool:
return issubclass(type(other),MorphismeQuotient) and (len(self._classe_morphismes-other._classe_morphismes) == 0 or
len(other._classe_morphismes-self._classe_morphismes) == 0)
def __hash__(self) -> int:
return hash((self.source,self.cible))
def __iter__(self) -> Generator[Morphisme,None,None]:
for m in self._classe_morphismes:
yield m
def __repr__(self) -> str:
def __str__(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})
return MorphismeQuotient(other.source,self.cible,{m2@m1 for m1 in other for m2 in self if m1.cible == m2.source})
class CategorieQuotient(CategorieLibre):
"""Définit ce qu'est une catégorie quotient.
"""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)."""
(les identités équivalentes définissent la relation d'équivalence sur les objets).
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 `CategorieAcyclique`."""
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é
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 self._obj_vers_classe:
for cle in 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]
obj_vers_classe[obj] = obj_vers_classe[cle]
classe_vers_obj[obj_vers_classe[obj]] |= {obj}
break
else:
self._obj_vers_classe[obj] = ObjetQuotient({obj})
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[cle]] |= {morph}
classe_vers_morph[morph_vers_classe[morph]] |= {morph}
break
else:
morph_vers_classe[morph] = len(list(morph_vers_classe.values()))+1
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(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]}
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()
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))
self |= set(self._obj_vers_classe.values())
# 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
def __getitem__(self,couple_sources_cibles:tuple) -> Generator[MorphismeQuotient,None,None]:
@property
def fonct_surjection(self) -> Foncteur:
'''Renvoie le foncteur de sujection canonique.'''
return self._fonct_surjection
@property
def cat_a_quotienter(self) -> Categorie:
'''Renvoie la catégorie à quotienter.'''
return self._categorie_a_quotienter
@unique_generator
def __getitem__(self,couple_sources_cibles:Tuple[Set[ObjetQuotient]]) -> Generator[MorphismeQuotient,None,None]:
sources,cibles = couple_sources_cibles
for source in sources:
for cible in cibles:
for fleche_elem in self.cat_a_quotienter[source,cible]:
yield self.fonct_surjection(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 les flèches élémentaires de la catégorie à quotienter.'''
raise NotImplementedError("Les classes filles doivent implementer cette methode afin de definir la relation d'equivalence par laquelle quotienter.")
\ 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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from config import *
if GRAPHVIZ_ENABLED:
import config
if config.GRAPHVIZ_ENABLED:
from graphviz import Digraph
import itertools
import functools
......@@ -34,8 +34,9 @@ class Foncteur(Morphisme):
self._app_objets = application_objets
self._app_morph = application_morphismes
for obj in self.source.objets:
self._app_morph[self.source.identite(obj)] = self.cible.identite(self._app_objets[obj])
if TOUJOURS_VERIFIER_COHERENCE:
if self.source.identite(obj) not in self._app_morph:
self._app_morph[self.source.identite(obj)] = self.cible.identite(self._app_objets[obj])
if config.TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
def __eq__(self,other:'Foncteur') -> bool:
......@@ -91,7 +92,8 @@ class Foncteur(Morphisme):
for g in self.source({f.cible},abs(self.source)):
if self(g@f) != self(g)@self(f):
raise Exception("Incoherence foncteur : l'image des composees n'est pas la composee des images \n"+\
str(self(g@f))+' != '+str(self(g)@self(f)))
"F(g o f)="+str(self(g@f))+' != '+"F(g) o F(f)="+str(self(g)@self(f))+'\nf = '+str(f)+'\ng = '+str(g)+\
"\nF(f) = "+str(self(f))+"\nF(g) = "+str(self(g)))
def __call__(self,param:Any)->Any:
'''Applique le foncteur à un objet ou un morphisme de la catégorie source.'''
......@@ -112,7 +114,7 @@ class Foncteur(Morphisme):
suffixe1_morph = "-1"
graph = Digraph('foncteur')
graph.attr(concentrate="true" if GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
graph.attr(concentrate="true" if config.GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
graph.attr(label="Foncteur "+self.nom)
with graph.subgraph(name='cluster_0') as cluster:
cluster.attr(label=self.source.nom)
......@@ -163,7 +165,7 @@ class Foncteur(Morphisme):
graph.edge(str(obj1)+suffixe1_objet,str(obj2)+suffixe2_objet,color="blue")
graph.render(destination)
if CLEAN_GRAPHVIZ_MODEL:
if config.CLEAN_GRAPHVIZ_MODEL:
import os
os.remove(destination)
......
......@@ -68,13 +68,9 @@ entre deux classes d'équivalence s'il existe au moins une flèche d'un objet d'
- 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
Modifier Morphisme pour que source, cible et is_identite soient read-only
Retirer tous les if TOUJOURS_VERIFIER_COHERENCE: verifier_coherence dans les classes filles, les méthodes sont de toute manière appelées par catégorie.
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