Commit 529db12a authored by Guillaume Sabbagh's avatar Guillaume Sabbagh
Browse files

Debut algo modification

parent e8789cc8
......@@ -12,9 +12,9 @@ from typing import *
def unique_generator(func):
"""Décorateur qui permet de renvoyer les flèches une seule fois par générateur."""
def wrapper(self, *args) -> Generator[any,None,None]:
def wrapper(self, *args, **kwargs) -> Generator[any,None,None]:
elem_generes = set()
for elem in func(self,*args):
for elem in func(self,*args,**kwargs):
if elem not in elem_generes:
elem_generes |= {elem}
yield elem
......@@ -218,6 +218,10 @@ class Categorie:
"""Renvoie un morphisme de `source` à `cible` s'il existe, renvoie None sinon."""
return next(self({source},{cible}),None)
def existe_morphisme_elementaire(self, source:Any, cible:Any) -> Union[Morphisme,None]:
"""Renvoie un morphisme élémentaire de `source` à `cible` s'il existe, renvoie None sinon."""
return next(self[{source},{cible}],None)
def existe_isomorphisme(self, source:Any, cible:Any) -> Union[tuple,None]:
"""Renvoie un isomorphisme de `source` à `cible` s'il existe sous la forme d'un couple (f,g) avec f:source->cible, et g:cible->source
renvoie None sinon."""
......@@ -263,7 +267,7 @@ class Categorie:
result = result.replace(',','\t,')
print(result)
def transformer_graphviz(self, destination:Union[str,None] = None, afficher_identites:bool = True, complet:bool = True, limite_fleches:int = 50):
def transformer_graphviz(self, destination:Union[str,None] = None, afficher_identites:bool = True, complet:bool = True, limite_fleches:int = 30):
"""Permet de visualiser la catégorie avec graphviz.
`complet` indique s'il faut afficher toutes les flèches ou seulement les flèches élémentaires.
`limite_fleches` est le nombre maximal de flèches entre deux objets qu'on s'autorise à afficher."""
......
......@@ -78,7 +78,6 @@ class CategorieHomologue(CatFinies):
bijections_fleches |= {bijections}
for bijection_fleche in produit_cartesien_generateurs(*bijections_fleches):
bij_fleche = dict([x for bij in bijection_fleche for x in bij.as_dict().items()])
print("yield")
yield Foncteur(source,cible,bij_obj,bij_fleche)
......
from Categorie import Categorie, unique_generator
from Morphisme import Morphisme
from ProduitGenerateurs import produit_cartesien_generateurs
from copy import copy
import itertools
import functools
......@@ -57,7 +58,7 @@ class CategorieLibre(Categorie):
return composees_resultat
return frozenset()
for chemin in enumerer_chemin_elem_sans_cycle(morphisme.source,morphisme.cible):
resultat = functools.reduce(lambda x,y:x@y,chemin)
resultat = functools.reduce(lambda x,y:y@x,chemin)
if resultat == morphisme:
for morph_elem in chemin:
yield morph_elem
......@@ -77,89 +78,157 @@ class CategorieLibre(Categorie):
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, noeuds_deja_visites:tuple=tuple()) -> frozenset():
def enumerer_composees_sans_cycle(self, source:Any, cible:Any) -> Generator[Morphisme,None,None]:
"""
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).
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).
"""
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 self.enumerer_composees_sans_cycle(morph.cible, cible, noeuds_deja_visites):
composees_resultat |= {composition_candidate@morph}
return composees_resultat
return frozenset()
def enumerer_chemins_sans_cycle(self, source:Any, cible:Any, noeuds_deja_visites:tuple=tuple()) -> frozenset:
for chemin in self.enumerer_chemins_sans_cycle(source,cible):
if len(chemin) == 1:
for e in self[{chemin[0]},{chemin[0]}]:
yield e
else:
generateurs = [self[{chemin[i]},{chemin[i+1]}] for i in range(len(chemin)-1)]
for prod in produit_cartesien_generateurs(*generateurs):
yield functools.reduce(lambda x,y : y@x, prod)
def enumerer_chemins_sans_cycle(self, source:Any, cible:Any) -> Generator[tuple,None,None]:
"""
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).
Génère 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 frozenset({(cible,)})
if source not in noeuds_deja_visites:
noeuds_deja_visites = noeuds_deja_visites + (source,)
chemins_resultat = frozenset()
for morph in self[{source},self.objets]:
for chemin_candidat in self.enumerer_chemins_sans_cycle(morph.cible, cible, noeuds_deja_visites):
chemins_resultat |= {(source,)+chemin_candidat}
return chemins_resultat
return frozenset()
# on fait un parcours en largeur
file_chemins = [(source,)]
while len(file_chemins) > 0:
chemin = file_chemins.pop(0)
if chemin[-1] == cible:
yield chemin
else:
for obj in self.objets:
if obj not in chemin:
if self.existe_morphisme_elementaire(chemin[-1],obj):
file_chemins += [chemin+(obj,)]
def trouver_cycles_minimaux(self, objet:Any, inclure_id:bool=True) -> frozenset():
"""Renvoie tous les cycles minimaux de morphismes élémentaires (qui ne contiennent aucun cycle)
de `objet` à `objet` (à l'exception de l'identité si `inclure_id` = False)."""
cycles = frozenset()
@unique_generator
def trouver_cycles_minimaux(self, objet:Any) -> Generator[Morphisme,None,None]:
"""Génère tous les cycles minimaux de morphismes élémentaires (qui ne contiennent aucun cycle)
de `objet` à `objet` différent de l'identité."""
for morph_pred in self[self.objets,{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)}
if morph_pred.source == objet:
yield morph_pred
else:
pred = morph_pred.source
for cycle_tronque in self.enumerer_composees_sans_cycle(objet, pred):
cycle = morph_pred@cycle_tronque
if not cycle.is_identite:
yield cycle
def enumerer_cycles(self, objet:Any, limite_profondeur:int = 27) -> frozenset:
def enumerer_cycles(self, objet:Any, limite_profondeur:int = 10) -> Generator[Morphisme,None,None]:
"""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 = set(self.trouver_cycles_minimaux(objet))
cycles_a_reduire = copy(cycles)
cycles_minimaux = copy(cycles)
cycles |= {self.identite(objet)}
for cycle in cycles:
yield cycle
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}
new_cycle = cycle_minimal@cycle_a_reduire
cycles_a_reduire |= {new_cycle}
cycles |= {new_cycle}
yield new_cycle
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:Any, cible:Any) -> Generator[Morphisme,None,None]:
"""Renvoie tous les morphismes composés allant de `source` à `cible`.
"""Génère 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(frozenset([e for chemin in chemins_sans_cycles for e in chemin]))
cycles = {x: self.enumerer_cycles(x) for x in noeuds}
dict_noeuds_cycles = dict() # {noeud : set(cycles)}
for chemin in chemins_sans_cycles:
cycles_concernes = [cycles[noeud] for noeud in chemin]
fleches_concernees = [set(self[{chemin[i]},{chemin[i+1]}]) for i in range(len(chemin)-1)]
for combinaison_fleches in itertools.product(*fleches_concernees):
for combinaison_cycle in itertools.product(*cycles_concernes):
composee = combinaison_cycle[0]
for i in range(len(combinaison_fleches)):
composee = combinaison_cycle[i+1]@combinaison_fleches[i]@composee
yield composee
cycles_concernes = []
for noeud in chemin:
if noeud not in dict_noeuds_cycles:
dict_noeuds_cycles[noeud] = set(self.enumerer_cycles(noeud))
cycles_concernes += [dict_noeuds_cycles[noeud]]
if len(chemin) >= 2:
fleches_concernees = [self[{chemin[i]},{chemin[i+1]}] for i in range(len(chemin)-1)]
for combinaison_fleches in produit_cartesien_generateurs(*fleches_concernees):
for combinaison_cycle in itertools.product(*cycles_concernes):
composee = combinaison_cycle[0]
for i in range(len(combinaison_fleches)):
composee = combinaison_cycle[i+1]@combinaison_fleches[i]@composee
yield composee
else:
# taille du chemin == 1
for e in cycles_concernes[0]:
yield e
def test_enumerer_chemins_sans_cycle():
from GrapheDeComposition import GC,MGC
GC = GC({'A','B','C','D','E','F'})
f,g,h,i,j,k,l,m,n = [MGC('A','B','f'),MGC('B','C','g'),MGC('A','A','h'),MGC('C','D','i'),
MGC('C','E','j'),MGC('E','F','k'),MGC('D','F','l'),MGC('D','D','m'),MGC('C','D','n')]
MGC.identifier_morphismes(h@h@h,h)
MGC.identifier_morphismes(m@m,m)
GC |= {f,g,h,i,j,k,l,m,n}
GC.transformer_graphviz()
for composee in GC.enumerer_chemins_sans_cycle('A','F'):
print(composee)
print()
def test_enumerer_composees_sans_cycle():
from GrapheDeComposition import GC,MGC
GC = GC({'A','B','C','D','E','F'})
f,g,h,i,j,k,l,m,n = [MGC('A','B','f'),MGC('B','C','g'),MGC('A','A','h'),MGC('C','D','i'),
MGC('C','E','j'),MGC('E','F','k'),MGC('D','F','l'),MGC('D','D','m'),MGC('C','D','n')]
MGC.identifier_morphismes(h@h@h,h)
MGC.identifier_morphismes(m@m,m)
GC |= {f,g,h,i,j,k,l,m,n}
GC.transformer_graphviz()
for composee in GC.enumerer_composees_sans_cycle('A','F'):
print(composee)
print()
def test_trouver_cycles_minimaux():
from GrapheDeComposition import GC,MGC
GC = GC({'A','B','C','D','E','F'})
f,g,h,i,j,k,l,m,n,o = [MGC('A','B','f'),MGC('B','C','g'),MGC('A','A','h'),MGC('C','D','i'),
MGC('C','E','j'),MGC('E','F','k'),MGC('D','F','l'),MGC('D','D','m'),MGC('C','D','n'),MGC('D','A','o')]
MGC.identifier_morphismes(h@h@h,h)
MGC.identifier_morphismes(m@m,m)
MGC.identifier_morphismes(o@i@g@f@o@i@g@f,o@i@g@f)
MGC.identifier_morphismes(o@i@g@f@o@n@g@f,o@i@g@f)
MGC.identifier_morphismes(o@n@g@f@o@i@g@f,o@n@g@f)
MGC.identifier_morphismes(o@n@g@f@o@n@g@f,o@n@g@f)
GC |= {f,g,h,i,j,k,l,m,n,o}
GC.transformer_graphviz(complet=False)
for composee in GC.trouver_cycles_minimaux('D'):
print(composee)
print()
def test_enumerer_composees():
from EnsFinis import EnsFinis
cat = EnsFinis({frozenset("ABCD"),frozenset("ABC")})
print(len(list(cat({frozenset("ABCD")},{frozenset("ABC")}))))
cat.transformer_graphviz()
if __name__ == '__main__':
test_enumerer_chemins_sans_cycle()
test_enumerer_composees_sans_cycle()
test_trouver_cycles_minimaux()
test_enumerer_composees()
\ No newline at end of file
......@@ -3,6 +3,7 @@ from TransformationNaturelle import CatTransfoNat,TransfoNat
from Foncteur import Foncteur
from Diagramme import DiagrammeConstant,DiagrammeObjets
from typing import *
from Cluster import ClusterActif,ProtoClusterActif
class Cocone(TransfoNat):
"""Un cocône sur D de nadir N est une transformation naturelle de D vers le foncteur constant Δ_N."""
......@@ -17,6 +18,9 @@ class Cocone(TransfoNat):
def as_ObjetCommaCategorie(self) -> ObjetCommaCategorie:
return ObjetCommaCategorie(0,self,self.nadir)
def as_Cluster(self) -> ClusterActif:
return ClusterActif(ProtoClusterActif(self.source,self.cible,self._composantes,"Cluster issu du cocône "+str(self)))
class ChampActif(CommaCategorie):
"""Le champ actif d'un diagramme(ou foncteur) D:P->Q est défini comme étant la comma-catégorie (D|Δ)
......
from Categorie import Categorie
from Morphisme import Morphisme
from GrapheDeComposition import GC,MGC
from Diagramme import Diagramme, DiagrammeIdentite
from ChampActif import ChampActif
from copy import copy
from typing import *
cat = GC(set("ABC"))
f,g = [MGC('A','B','f'),MGC('A','C','g')]
cat |= {f,g}
diag = DiagrammeIdentite(cat)
c_a = ChampActif(diag)
diag.transformer_graphviz()
c_a.transformer_graphviz()
# diag = DiagrammeIdentite(cat)
# c_a = ChampActif(diag)
# diag.transformer_graphviz()
# c_a.transformer_graphviz()
cat2 = copy(cat)
cat2 |= {'L'}
h,i,j = [MGC('A','L','h'),MGC('B','L','i'),MGC('C','L','j')]
cat2 |= {h,i,j}
# cat2 = copy(cat)
# cat2 |= {'L'}
# h,i,j = [MGC('A','L','h'),MGC('B','L','i'),MGC('C','L','j')]
# cat2 |= {h,i,j}
diag2 = Diagramme(cat,cat2,{'A':'A','B':'B','C':'C'},{f:f,g:g})
MGC.identifier_morphismes(i@f,h)
MGC.identifier_morphismes(j@g,h)
# diag2 = Diagramme(cat,cat2,{'A':'A','B':'B','C':'C'},{f:f,g:g})
# MGC.identifier_morphismes(i@f,h)
# MGC.identifier_morphismes(j@g,h)
diag2.transformer_graphviz()
c_a2 = ChampActif(diag2)
c_a2.transformer_graphviz()
# diag2.transformer_graphviz()
# c_a2 = ChampActif(diag2)
# c_a2.transformer_graphviz()
def modification(gc: GC, option_ajout:Tuple[Sequence[Any],Sequence[Morphisme]], option_elimination:Sequence[Diagramme], option_complex_agissante:Sequence[Diagramme]) -> GC:
nouveau_gc = copy(gc)
nouveaux_obj, nouvelles_fleches = option_ajout
nouveau_gc |= set(nouveaux_obj)
nouveau_gc |= set(nouvelles_fleches)
nouveau_gc -= set(option_elimination)
for
return nouveau_gc
cat.transformer_graphviz()
cat2 = modification(cat,({'S','T'},{MGC('S','T','h')}),{'C'},{})
cat2.transformer_graphviz()
......@@ -33,6 +33,21 @@ cat_hom.transformer_graphviz()
from Cluster import CategorieClusters
cocone_limite_d1 = Cocone(list(sous_c_p1.objets)[0])
cocone_limite_d1.transformer_graphviz()
cat_clust = CategorieClusters(cat,{d1,d2})
SousCategoriePleine(cat_clust,{d1,d2}).transformer_graphviz()
print(cat_clust.existe_isomorphisme(d1,d2))
\ No newline at end of file
for cocone_d2 in c_p2.objets:
if Cocone(cocone_d2).nadir == cocone_limite_d1.nadir:
Cocone(cocone_d2).transformer_graphviz()
for cluster in cat_clust({d1},{d2}):
print(type(Cocone(cocone_d2).as_Cluster()))
print(type(cluster))
print(cocone_limite_d1 == Cocone(cocone_d2).as_Cluster()@cluster)
cluster.transformer_graphviz()
(Cocone(cocone_d2).as_Cluster()@cluster).transformer_graphviz()
\ No newline at end of file
......@@ -57,4 +57,6 @@ cat[a,b] appelle cat(a,b) par défaut sauf pour le graphe de composition
utiliser existe_morphisme plutôt que self({cocone_courant},cocones_restants) dans la recherche de limites
faire une seule classe catégorie homologue en créant une méthode sommet dans cone et cocone;
\ No newline at end of file
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
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