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

Début de la transformation du code dans sa nouvelle structure

parent 98c9bcb5
This diff is collapsed.
......@@ -116,6 +116,7 @@ def main():
cat_fleurie.ajouter_diagramme_interet(diag2)
cat_fleurie.transformer_graphviz(afficher_identites=True)
cat_fleurie.csv_loi_de_composition("lois de composition/loi1.csv",';')
if __name__ == '__main__':
......
from Categorie import Categorie
from copy import copy
import itertools
from config import *
if GRAPHVIZ_ENABLED:
from graphviz import Digraph
class CategorieLibre(Categorie):
"""
Classe abstraite qui définit ce qu'est une catégorie libre engendrée par un graphe de composition.
Cette classe surcharge l'opérateur __call__, pour calculer C(a,b),
on fait tous les chemins de a vers b et on renvoie l'ensemble de ces composées.
Les classes filles doivent implémenter (en plus de la méthode identite) les méthodes :
- morph_sortants(self, source) : renvoie l'ensemble des morphismes sortants de source
- morph_entrants(self, cible) : renvoie l'ensemble des morphismes entrants dans la cible
ces deux méthodes définissent le graphe sous-jacent avec les objets de la catégorie
"""
def __init__(self, nom="Categorie libre"):
Categorie.__init__(self,nom)
def fleches_elem(self, source, cible, inclure_id = True):
"""
Renvoie la liste de flèches élémentaires (flèches du graphe sous-jacent de source à cible).
Si inclure_id=False, alors on exclut les identités.
"""
if not inclure_id:
return self.morph_sortants(source)&self.morph_entrants(cible)-{self.identites[source]}
return self.morph_sortants(source)&self.morph_entrants(cible)
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
for morph in self(self.objets,self.objets):
if len(morph) == 1:
if morph not in self.morph_sortants(morph.source):
raise Exception("Incoherence CategorieLibre : le morphisme "+str(morph)+" ne sort pas de "+str(morph.source))
if morph not in self.morph_entrants(morph.cible):
raise Exception("Incoherence CategorieLibre : le morphisme "+str(morph)+" n'entre pas dans "+str(morph.cible))
# on vérifie que tous les morphismes entrants et sortants sont des morphismes
for obj in self.objets:
if len(self.morph_sortants(obj)-{m for m in self(self.objets,self.objets) if len(m) == 1}) > 0:
raise Exception("Incoherence CategorieLibre : le(s) morphisme(s) sortant(s) "+str(self.morph_sortants(obj)-self.morphismes)+\
"n'est pas un morphisme de la catégorie")
if len(self.morph_entrants(obj)-{m for m in self(self.objets,self.objets) if len(m) == 1}) > 0:
raise Exception("Incoherence CategorieLibre : le(s) morphisme(s) entrant(s) "+str(self.morph_entrants(obj)-self.morphismes)+\
"n'est pas un morphisme de la catégorie")
def __call__(self,*args):
"""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}
C({a_i},b) renvoie l'ensemble des flèches d'un élément de {a_i} vers b
C(a,{b_i}) renvoie l'ensemble des flèches de a vers un élément de {b_i}
C(a,b) renvoie l'ensemble des flèches de a vers b,
C(a) renvoie l'identité de a.
Pour la catégorie libre, on doit énumérer tous les chemins et les composer.
"""
if len(args) == 1:
# on doit renvoyer l'identité de a
if args[0] not in self.identites:
raise Exception("Categorie __call__ : tentative de recuperer l'identite d'un objet qui n'est pas dans la catégorie")
return self.identites[args[0]]
if len(args) > 2:
raise Exception("Categorie __call__ : plus de deux arguments passés")
source,cible = args
if source in self.objets:
# source est de type a
source = {source}
if cible in self.objets:
# cible est de type b
cible = {cible}
#maintenant tout est de la forme {a_i} vers {b_i}
return {morph for a in source for b in cible for morph in self.enumerer_composees(a,b)}
def enumerer_composees_sans_cycle(self, source, cible, noeuds_deja_visites=tuple()):
"""
Renvoie tous les morphismes composés allant de source à cible ne contenant aucun cycle (on ne passe jamais deux fois par le même noeud).
"""
if source == cible:
return {self.identite(source)}
if source not in noeuds_deja_visites:
noeuds_deja_visites = noeuds_deja_visites + (source,)
composees_resultat = set()
for morph in self.morph_sortants(source):
for composition_candidate in self.enumerer_composees_sans_cycle(morph.cible, cible, noeuds_deja_visites):
composees_resultat |= {composition_candidate@morph}
return composees_resultat
return set()
def enumerer_chemins_sans_cycle(self, source, cible, noeuds_deja_visites=tuple()):
"""
Renvoie tous les chemins (liste de noeuds) allant de source à cible ne contenant aucun cycle (on ne passe jamais deux fois par le même noeud).
"""
if source == cible:
return {(cible,)}
if source not in noeuds_deja_visites:
noeuds_deja_visites = noeuds_deja_visites + (source,)
chemins_resultat = set()
for morph in self.morph_sortants(source):
for chemin_candidat in self.enumerer_chemins_sans_cycle(morph.cible, cible, noeuds_deja_visites):
chemins_resultat |= {(source,)+chemin_candidat}
return chemins_resultat
return set()
def existe_composee(self,source,cible, noeuds_deja_visites=tuple()):
"""Trouve une composée de source à cible si elle existe, renvoie None sinon"""
if source == cible:
return self.identite(source)
if source not in noeuds_deja_visites:
noeuds_deja_visites = noeuds_deja_visites + (source,)
for morph in self.morph_sortants(source):
composee = self.existe_composee(morph.cible, cible,noeuds_deja_visites)
if composee != None:
return composee@morph
return None
def trouver_cycles_minimaux(self, objet, inclure_id=True):
"""Renvoie tous les cycles minimaux de morphismes élémentaires (qui ne contiennent aucun cycle)
de objet à objet (à l'exception de l'identité si iclure_id = False)."""
cycles = set()
for morph_pred in self.morph_entrants(objet):
pred = morph_pred.source
cycles_tronques = self.enumerer_composees_sans_cycle(objet, pred)
for cycle_tronque in cycles_tronques:
cycle = morph_pred@cycle_tronque
if cycle not in cycles:
cycles |= {cycle}
if inclure_id:
return cycles|{self.identite(objet)}
return cycles-{self.identite(objet)}
def enumerer_cycles(self, objet, limite_profondeur = 4):
"""Enumère toutes les compositions de objet à objet.
Si f et g sont des cycles minimaux, on doit énumérer tous les mots d'alphabet {f,g}.
Pour ça on s'intéresse aux compositions qui se réduisent en composition déjà générées.
Si f ne se réduit pas, on regarde ff et fg, puis si ceux là non plus fff, ffg, fgf,fgg etc.
On s'arrête à la limite de profondeur spécifiée.
"""
cycles = self.trouver_cycles_minimaux(objet,inclure_id=False)
cycles_a_reduire = copy(cycles)
cycles_minimaux = copy(cycles)
cycles |= {self.identite(objet)}
for profondeur in range(limite_profondeur):
for cycle_a_reduire in copy(cycles_a_reduire):
for cycle_minimal in cycles_minimaux:
if cycle_minimal@cycle_a_reduire not in cycles:
cycles_a_reduire |= {cycle_minimal@cycle_a_reduire}
cycles |= {cycle_minimal@cycle_a_reduire}
cycles_a_reduire -= {cycle_a_reduire}
if len(cycles_a_reduire) > 0:
print("Warning CategorieLibre : limite de profondeur atteinte dans l'enumeration des cycles")
if DEBUG_LOI_DE_COMPOSITION:
print(cycles_a_reduire)
return cycles
def enumerer_composees(self, source, cible):
"""Renvoie tous les morphismes composés allant de source à cible.
1) On trouve les chemins sans cycle.
2) Pour tous les noeuds U du chemin on énumère les cycles de U à U.
3) Pour chaque chemin sans cycle, pour chaque noeud qui a au moins un cycle, on duplique le chemin d'autant de cycles que nécessaires."""
chemins_sans_cycles = self.enumerer_chemins_sans_cycle(source, cible)
noeuds = list(set([e for chemin in chemins_sans_cycles for e in chemin]))
cycles = {x: self.enumerer_cycles(x) for x in noeuds}
tous_les_chemins = set()
for chemin in chemins_sans_cycles:
composees_obtenues = set(cycles[chemin[0]]) #on commence avec les cycles de la source du chemin
for i in range(len(chemin)):
noeud = chemin[i]
if i < len(chemin)-1:
noeud_suivant = chemin[i+1]
for fleche in self.fleches_elem(noeud,noeud_suivant):
composees_obtenues = {fleche@composee for composee in composees_obtenues}
if len(chemin) > 1: #pour que noeud_suivant soit defini
for composee in copy(composees_obtenues):
for cycle in cycles[noeud_suivant]:
nouvelle_composee = cycle@composee
composees_obtenues |= {nouvelle_composee}
tous_les_chemins |= composees_obtenues
return tous_les_chemins
def transformer_graphviz(self, complet=True, destination=None, afficher_identites=False):
"""
Permet de visualiser la catégorie libre avec graphviz.
Les flèches élémentaires (flèches du graphe de composition) sont en noir.
Les flèches composéées sont en gris.
"""
Categorie.nb_viz += 1
if destination == None:
destination = "graphviz/categorie" + str(Categorie.nb_viz)
graph = Digraph('categorie')
graph.attr(concentrate="true" if GRAPHVIZ_CONCENTRATE_GRAPHS else "false")
graph.attr(label=self.nom)
for o in self.objets:
graph.node(str(o))
if complet:
fleches = self(self.objets,self.objets)
for fleche in fleches:
if afficher_identites or not fleche.is_identite:
if len(fleche) == 1:
graph.edge(str(fleche.source), str(fleche.cible), label=str(fleche), weight="1000")
else:
graph.edge(str(fleche.source), str(fleche.cible), label=str(fleche), color="grey77")
else:
for fleche in self.morphismes:
if afficher_identites or not fleche.is_identite:
graph.edge(str(fleche.source), str(fleche.cible), label=str(fleche))
graph.render(destination)
if CLEAN_GRAPHVIZ_MODEL:
import os
os.remove(destination)
\ No newline at end of file
from Morphisme import Morphisme
from Categorie import Categorie
class ObjetCommaCategorie:
"""Soit C une catégorie, T et S deux foncteurs de E dans C et D dans C.
Un objet de la comma-categorie (T|S) est un triplet (e,f,d) où e est un objet de E, f une flèche de T(e) vers S(d) et d un objet de D.
(cf. Mac Lane "Categories for the working mathematician" P.45)
"""
def __init__(self,e,f,d):
self.e = e
self.f = f
self.d = d
def __repr__(self):
return '('+str(self.e)+','+str(self.f)+','+str(self.d)+')'
class FlecheCommaCategorie(Morphisme):
"""Soit C une catégorie, T et S deux foncteurs de E dans C et D dans C.
Une flèche de la comma-categorie (T|S) entre deux objets (e,f,d) et (e',f',d') est un couple (k,h)
où k est une flèche de E(e,e') et h est une flèche de D(d,d') tel que la carré
T(e) -T(k)-> T(e')
| |
f f'
| |
v v
S(d) -S(h)-> S(d')
commute.
(cf. Mac Lane "Categories for the working mathematician" P.45)
"""
def __init__(self,source,cible,k,h):
self.k = k
self.h = h
Morphisme.__init__(self,source,cible,'('+str(self.k)+','+str(self.h)+')')
def __repr__(self):
return '('+str(self.k)+','+str(self.h)+')'
def verifier_coherence(self,T,S):
if T.cat_cible != S.cat_cible:
raise Exception("Incoherence FlecheCommaCategorie : T et S de cibles differentes : "+str(T)+" != "+str(S))
categorie = T.cat_cible
if categorie.Composee(T(self.k),self.cible.f) != categorie.Composee(self.source.f,S(self.h)):
raise Exception("Incoherence FlecheCommaCategorie : le carre ne commute pas, "+str(self.cible.f)+" o "+str(T(self.k))+" != "+str(S(self.h))+" o "+str(self.source.f))
class CommaCategorie(Categorie):
"""
Soit C une catégorie, T et S deux foncteurs de E dans C et D dans C.
Cette catégorie est (T|S).
Voir Mac Lane "Categories for the working mathematician" P.45
"""
def __init__(self,T,S):
if T.cat_cible != S.cat_cible:
raise Exception("Incoherence CommaCategorie : T et S de cibles differentes : "+str(T)+" != "+str(S))
self.C = T.cat_cible
\ No newline at end of file
from Categorie import Categorie
from CategorieLibre import CategorieLibre
from Morphisme import Morphisme
from collections import defaultdict
class MorphismeGrapheComposition(Morphisme):
"""
Un morphisme dans un graphe de composition est un chemin dans ce graphe.
Deux morphismes peuvent être identifiés dans le graphe de composition.
Le chemin est représenté par un tuple de morphismes de graphe de composition.
Si un morphisme appartient à deux catégories, les lois de composition des deux graphes de composition sont partagées.
"""
loi_de_composition = dict() # on associe à certains chemins un autre chemin aussi long ou plus court
#{tuple de morphismes : tuple de morphismes}
def __simplifier_chemin(morph1,morph2):
"""
On simplifie le chemin défini par la composition des deux morphismes grâce à la loi de composition.
Hypothèses faites sur les données : il n'y a pas d'identité dans morph1 et morph2.
On renvoie un tuple de morphismes.
"""
chemin = morph1.__chemin+morph2.__chemin# on unpack les différents chemins en un seul chemin
while True: #on continue tant qu'on a simplifié le chemin
for nb_morphismes_consideres in range(len(chemin), 1, -1):
for offset in range(0, len(chemin) - nb_morphismes_consideres + 1):
sous_composee = chemin[offset:offset + nb_morphismes_consideres]
if sous_composee in MorphismeGrapheComposition.loi_de_composition: # si il y a une simplification dans la loi de composition
chemin = chemin[0:offset] + (MorphismeGrapheComposition.loi_de_composition[sous_composee],)\
+ chemin[offset + nb_morphismes_consideres:]
break
else:
continue
break
else:
break #on quitte si on a jamais break avant (si on a jamais simplifié)
return chemin
def __init__(self, source, cible, representant, is_identite = False):
self.__chemin = (self,) # le chemin sera overwrite par matmul si le morphisme est une composition
Morphisme.__init__(self, source, cible, representant, is_identite)
def __getitem__(self,entier):
return self.__chemin[entier]
def __iter__(self):
for morph in self.__chemin:
yield morph
def __len__(self):
return len(self.__chemin)
def __matmul__(self,other):
"""
self @ other
"""
if not issubclass(type(other),MorphismeGrapheComposition):
raise Exception("Composition d'un morphisme de graphe de composition avec un morphisme de type different : "+str(self)+" o "+str(other))
if self.source != other.cible:
raise Exception("Composition de morphismes pas composables : "+str(self)+" o "+str(other))
if self.is_identite:
return other
if other.is_identite:
return self
# à partir d'ici on doit instancier un nouveau morphisme
chemin_simplifie = MorphismeGrapheComposition.__simplifier_chemin(self,other)
if len(chemin_simplifie) == 1: # si on a simplifié à un morphisme on le renvoie
return chemin_simplifie[0]
else: #sinon on doit créer un morphisme dont le chemin est celui qu'on a trouvé
nouveau_morph = MorphismeGrapheComposition(other.source,self.cible,representant=str(self)+'o'+str(other),is_identite=False)
nouveau_morph.__chemin = chemin_simplifie
return nouveau_morph
def __eq__(self,other):
return self.__chemin == other.__chemin
def __hash__(self):
if len(self.__chemin) == 1:
return super().__hash__()
return hash(self.__chemin)
class ModeleGrapheComposition(CategorieLibre):
"""
Un graphe de composition pour nous est simplement un graphe où certaines certaines composées sont identifiées.
Un modèle de graphe de composition est la catégorie libre engendrée par ce graphe de composition.
Concrètement, pour un modèle de graphe de composition, on peut :
- ajouter un objet ou un morphisme
- faire commuter un diagramme qui a pour cible ce modèle pour identifier des flèches
"""
def __init__(self,nom='Modèle de graphe de composition'):
CategorieLibre.__init__(self,nom)
self.__identites = dict()
self.__morph_entrants = defaultdict(set)
self.__morph_sortants = defaultdict(set)
def identite(self,objet):
return self.__identites[objet]
def ajouter_objet(self,objet):
Categorie.ajouter_objet(self,objet)
self.__identites[objet] = MorphismeGrapheComposition(objet,objet,'Id'+str(objet),True)
self.__morph_entrants[objet] |= {self.__identites[objet]}
self.__morph_sortants[objet] |= {self.__identites[objet]}
def ajouter_morphisme(self,morphisme):
Categorie.ajouter_morphisme(self,morphisme)
self.__morph_entrants[morphisme.cible] |= {morphisme}
self.__morph_sortants[morphisme.source] |= {morphisme}
def supprimer_objet(self, objet):
Categorie.supprimer_objet(self,objet)
del self.__identites[objet]
def supprimer_morphisme(self, morphisme):
Categorie.supprimer_morphisme(self,objet)
self.__morph_entrants[morphisme.cible] -= {morphisme}
self.__morph_sortants[morphisme.source] -= {morphisme}
def morph_entrants(self,objet):
return self.__morph_entrants[objet]
def morph_sortants(self,objet):
return self.__morph_sortants[objet]
def main():
MGC = ModeleGrapheComposition()
MGC += {'A','B','C'}
morphismes = [MorphismeGrapheComposition('A','B','f'),MorphismeGrapheComposition('B','A','g')]
MGC += morphismes
MGC.transformer_graphviz()
if __name__ == '__main__':
main()
\ No newline at end of file
......@@ -3,17 +3,21 @@
class Morphisme:
"""Appeler le constructeur de Morphisme instancie forcément un nouveau morphisme."""
"""
Classe abstraite qui définit ce qu'est un morphisme dans une catégorie.
Un morphisme a une source et une cible, il contient un booléen is_identite qui indique si le morphisme est une identité.
Toutes les classes filles de Morphisme doivent implémenter les opérateur suivants :
- __matmul__ : g@f est la composée gof
"""
id = 0
def __init__(self, source, cible, representant = None, is_identite = False):
"""Representant est un objet ou un str qui représente le morphisme pour l'affichage.
Attention a instancier l'identité d'un objet une seule fois."""
"""Representant est un objet ou un str qui représente le morphisme pour l'affichage."""
self.id = Morphisme.id
Morphisme.id += 1
self.source = source
self.cible = cible
self.is_identite = is_identite
self.composee = tuple([self])
if representant == None:
self.representant = str(self.id)
else:
......@@ -22,92 +26,17 @@ class Morphisme:
raise Exception("Identite de source et cible differente : "+str(source)+"->"+str(cible))
def __str__(self):
if self.representant != None:
return str(self.representant)
return str(self.id)
return str(self.representant)
def pretty_print(self):
return str(self.representant)+' : '+str(self.source)+" -> "+str(self.cible)
def __repr__(self):
return self.pretty_print()#+","+super().__repr__().split('object at ')[-1].split('>')[0]
def __len__(self):
return len(self.composee)
def __getitem__(self,key):
return self.composee[key]
def __iter__(self):
for morph in self.composee:
yield morph
def objets_traverses(self):
"""Renvoie tous les objets qui interviennent dans le morphisme dans l'ordre."""
result = []
for morph in self.composee:
if morph.source not in result:
result += [morph.source]
if morph.cible not in result:
result += [morph.cible]
return result
class Composition(Morphisme):
"""Appeler le constructeur de Composition n'instancie pas forcément une nouvelle composition.
Comparer les composée avec les opérateurs == et !=
/!\ si une identité incohérente est placée dans une composition valide, elle sera simplement omise /!\
"""
def __new__(cls,*morphismes):
if len(morphismes) == 0:
raise Exception("Composition de 0 morphismes")
backup = morphismes
morphismes = [m for m in morphismes if not m.is_identite] #on supprime les identités
if len(morphismes) == 0:
for i in range(1,len(backup)):
if backup[i-1] != backup[i]:
raise Exception("Composition d'identites differentes"+str(backup))
return backup[0]
elif len(morphismes) == 1:
return morphismes[0]
else:
instance = super(Composition, cls).__new__(cls)
return instance
def __init__(self,*morphismes):
morphismes = [m for m in morphismes if not m.is_identite] #on supprime les identités
Morphisme.__init__(self,morphismes[0].source,morphismes[-1].cible)
self.composee = list(map(lambda x:[x] if type(x) is Morphisme else x.composee,morphismes))
self.composee = tuple([e for c in self.composee for e in c])
#on explose la composee en la liste de ses morphismes constituants
##on vérifie que la composition est respectée
for i in range(1,len(self.composee)):
if self.composee[i-1].cible != self.composee[i].source:
raise Exception("Composition erronee : "+str(self.composee[i-1])+" composee avec "+str(self.composee[i]))
##on ajoute un représentant si c'est possible
if None not in [e.representant for e in self.composee]:
self.representant = 'o'.join(map(str,[e.representant for e in self.composee][::-1]))
else:
self.representant = None
def __eq__(self,other):
if issubclass(type(other),Morphisme):
return self.composee == other.composee
else:
return False
def __hash__(self):
return hash(self.composee)
def __str__(self):
return ' >> '.join(map(str,self.composee))
def __matmul__(self,other):
raise NotImplementedError("Les classes filles doivent implémenter la composition des morphismes.")
def __call__(self, param = None):
for morph in self.composee:
param = morph(param)
return param
def main():
m1 = Morphisme(1,2)
......
import Categorie,CategorieAleatoire
import Diagramme
from Diagramme import *
import CategorieCones,CategorieCocones,CategorieFleurie,CategorieClusters
import Bouquet
import random
......@@ -198,37 +198,71 @@ import Cluster
# cat_cluster.ajouter_diagramme_interet(diag)
# cat_cluster.transformer_graphviz()
cat = Categorie.Categorie("Test cone cluster")
cat.ajouter_objets("ABC")
a,b,c,d = [Morphisme('A','B','a'),Morphisme('A','C','b'),Morphisme('B','C','c'),Morphisme('C','C','d')]
cat.ajouter_morphismes([a,b,c,d])
diag = Triangle(cat,"CCC",[d,d,d])
diag.faire_commuter()
cat.transformer_graphviz()
cat_index = Categorie.Categorie()
cat_index.ajouter_objets("ABCD")
e,f,g = [Morphisme('A','B','e'),Morphisme('B','C','f'),Morphisme('C','D','g')]
cat_index.ajouter_morphismes([e,f,g])
cat_index.transformer_graphviz()
fonct = Foncteur.Foncteur(cat_index,cat,{"A":"B","B":"C","C":"C","D":"C"},{e:c,f:d,g:d})
fonct.transformer_graphviz()
cat_cones = CategorieCones.CategorieCones(fonct)
cat_cones.transformer_graphviz()
diag_c = DiagrammeObjets(cat,['B'])
cluster = Cluster.ClusterActif(diag_c,fonct)
cluster.transformer_graphviz()
# cat_cluster.transformer_graphviz()
# cat_cluster = CategorieClusters.CategorieClustersActifs(cat)
# cat_cluster.ajouter_diagramme_interet(fonct)
# cat_cluster.transformer_graphviz()
# cat = Categorie.Categorie("Test cocone cluster")
# cat.ajouter_objets("ABC")
# a,b,c,d = [Morphisme('A','B','a'),Morphisme('A','C','b'),Morphisme('B','C','c'),Morphisme('C','C','d')]
# a,b,c,d = [Morphisme('B','A','a'),Morphisme('C','A','b'),Morphisme('C','B','c'),Morphisme('C','C','d')]
# cat.ajouter_morphismes([a,b,c,d])