Commit 78add5ea authored by Guillaume Sabbagh's avatar Guillaume Sabbagh
Browse files

Catégorie Squelettique, version catégorielle de catégorie de graphes acycliques.

parent 86fb5a34
from Morphisme import Morphisme
from Categorie import Categorie
from collections import defaultdict
from config import *
if GRAPHVIZ_ENABLED:
from graphviz import Digraph
from typing import *
class Graphe:
"""Un graphe (sous_entendu orienté) est constitué de noeuds et d'arcs.
Entre deux noeuds il y au plus un arc."""
_id = 0
def __init__(self,nom:Union[str,None] = None):
Graphe._id += 1
self._id = Graphe._id
if nom == None:
self.nom = "Graphe"+str(self._id)
else:
self.nom = nom
self._noeuds = set() # read only
self._successeurs = defaultdict(set)
self._predecesseurs = defaultdict(set)
def verifier_coherence(self):
for noeud in self._successeurs:
if noeud not in self._noeuds:
raise Exception("Incoherence Graphe : structure incoherente.")
for succ in self._successeurs[noeud]:
if succ not in self.noeuds:
raise Exception("Incoherence Graphe : structure incoherente.")
for noeud in self._predecesseurs:
if noeud not in self._noeuds:
raise Exception("Incoherence Graphe : structure incoherente.")
for pred in self._predecesseurs[noeud]:
if pred not in self.noeuds:
raise Exception("Incoherence Graphe : structure incoherente.")
@property
def noeuds(self):
return self._noeuds
def succ(self, noeud:Hashable) -> set:
return self._successeurs[noeud]
def pred(self, noeud:Hashable) -> set:
return self._predecesseurs[noeud]
def ajouter_noeud(self, noeud:Hashable):
self._noeuds |= {noeud}
if TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
def ajouter_noeuds(self, noeuds:set):
self._noeuds |= noeuds
if TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
def ajouter_arc(self, source:Hashable, cible:Hashable):
self._successeurs[source] |= {cible}
self._predecesseurs[cible] |= {source}
if TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
def supprimer_noeud(self, noeud:Hashable):
if noeud in self._successeurs:
del self._successeurs[noeud]
if noeud in self._predecesseurs:
del self._predecesseurs[noeud]
for n in self._successeurs:
self._successeurs[n] -= {noeud}
for n in self._predecesseurs:
self._predecesseurs[n] -= {noeud}
self._noeuds -= {noeud}
def supprimer_noeuds(self, noeuds:Iterable):
for noeud in noeuds:
self.supprimer_noeud(noeud)
def supprimer_arc(self, source:Hashable, cible:Hashable):
if cible in self._successeurs[source]:
self._successeurs[source] -= {cible}
if source in self._predecesseurs[cible]:
self._predecesseurs[cible] -= {source}
if TOUJOURS_VERIFIER_COHERENCE:
self.verifier_coherence()
def transformer_graphviz(self, destination:Union[str,None] = None):
'''Permet de visualiser le graphe avec graphviz.'''
if destination == None:
destination = "graphviz/"+type(self).__name__+ str(self._id)
graph = Digraph('categorie')
graph.attr(concentrate="true" if GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
graph.attr(label=self.nom)
for o in self.noeuds:
graph.node(str(o))
for succ in self.succ(o):
graph.edge(str(o), str(succ))
graph.render(destination)
if CLEAN_GRAPHVIZ_MODEL:
import os
os.remove(destination)
def test_Graphe():
graphe = Graphe("test")
graphe.ajouter_noeuds({1,2,3,4,5})
graphe.ajouter_arc(1,2)
graphe.ajouter_arc(2,3)
graphe.ajouter_arc(3,4)
graphe.ajouter_arc(4,5)
graphe.ajouter_arc(1,4)
graphe.ajouter_arc(4,2)
graphe.ajouter_arc(4,3)
graphe.ajouter_arc(5,1)
graphe.transformer_graphviz()
if __name__ == '__main__':
test_Graphe()
\ No newline at end of file
from Morphisme import Morphisme
from Categorie import Categorie
from CategorieGraphes import Graphe
import copy
from config import *
from typing import *
class GrapheAcyclique(Graphe):
_id = 0
def __init__(self, nom:Union[str,None]):
GrapheAcyclique._id += 1
self._id = GrapheAcyclique._id
if nom == None:
nom = "GrapheAcyclique"+str(self._id)
Graphe.__init__(self,nom)
self.verifier_coherence()
def tri_topologique(self) -> list:
"""Renvoie un tri topologique du graphe, c'est-à-dire une liste de noeuds
tels qu'un noeud en précède un autre uniquement s'il n'y a pas d'arc du deuxième vers le premier."""
copie = copy.deepcopy(self)
result = []
while len(copie.noeuds) > 0:
noeuds_sans_predecesseurs = [noeud for noeud in copie.noeuds if len(copie.pred(noeud)) == 0]
if len(noeuds_sans_predecesseurs) == 0:
if GRAPHVIZ_ENABLED:
copie.transformer_graphviz()
raise Exception("Incoherence GrapheAcyclique : le graphe n'est pas acyclique")
result += noeuds_sans_predecesseurs
copie.supprimer_noeuds(noeuds_sans_predecesseurs)
copie.transformer_graphviz()
return result
def verifier_coherence(self):
Graphe.verifier_coherence(self)
self.tri_topologique()
def test_GrapheAcyclique():
graphe = GrapheAcyclique("test")
graphe.ajouter_noeuds({1,2,3,4,5})
graphe.ajouter_arc(1,2)
graphe.ajouter_arc(2,3)
graphe.ajouter_arc(3,4)
graphe.ajouter_arc(2,3)
graphe.ajouter_arc(2,4)
graphe.transformer_graphviz()
if __name__ == '__main__':
test_GrapheAcyclique()
\ No newline at end of file
......@@ -3,7 +3,8 @@ 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 +20,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,7 +47,16 @@ class CategoriePreordre(Categorie):
for cible in cibles:
if self.existe_morphisme(source,cible) != None:
yield MorphismePreordre(source,cible)
CatFine = CategoriePreordre
def test_CategoriePreordre():
cat = CategoriePreordre()
cat |= set("ABCDEF")
f,g,h,i,j,k,l = [MPO('A','B','f'),MPO('B','C','g'),MPO('D','E','h'),MPO('E','F','i'),MPO('A','D','j'),MPO('B','E','k'),MPO('C','F','l')]
cat |= {f,g,h,i,j,k,l}
cat.transformer_graphviz()
class CategoriePreordreEngendree(CategoriePreordre):
"""La catégorie préordre engendrée par une catégorie C est une sous-catégorie maximale C' de C
......@@ -49,7 +69,7 @@ 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 test_CategoriePreordreEngendree():
from GrapheDeComposition import GC,MGC
gc = GC()
......
from Categorie import Categorie
from CategoriePreordre import CatFine,CategoriePreordreEngendree
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.deepcopy(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(CategoriePreordreEngendree,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, une Exception est levée."""
def __init__(self, categorie:Categorie, nom:str = None):
CategorieSquelettique.__init__(self,set(), "Catégorie squelettique engendrée par "+str(categorie) if nom == None else nom)
CategoriePreordreEngendree.__init__(self,categorie, "Catégorie squelettique engendrée par "+str(categorie) if nom == None else nom)
CategorieSquelettique.verifier_coherence(self)
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('C','F','l'),MGC('B','D','m'),MGC('C','E','n')]
gc |= {f,g,h,i,j,k,l,m,n}
gc.transformer_graphviz()
c_po = CategorieSquelettiqueEngendree(gc)
c_po.transformer_graphviz()
print(c_po.tri_topologique())
if __name__ == '__main__':
test_CategorieSquelettiqueEgendree()
\ 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