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

Revert "Début correction CatégorieAléatoire pour inclure les identités dans la...

Revert "Début correction CatégorieAléatoire pour inclure les identités dans la table de loi de composition"

This reverts commit c54c6b35.
parent c54c6b35
from Categorie import Categorie
from Foncteur import Foncteur
from EnsFinis import EnsFinis,Application
from ProduitGenerateurs import produit_cartesien_generateurs
import itertools
from typing import *
......@@ -18,17 +17,19 @@ class CatFinies(Categorie):
# cat_source et cat_cible sont des catégories, on cherche tous les foncteurs de cat_source vers cat_cible
ens_objets = EnsFinis({cat_source.objets,cat_cible.objets})
for app_obj in ens_objets({cat_source.objets},{cat_cible.objets}):
print(app_obj)
app_fleche_foncteur = dict() # à chaque couple d'objets on associe un ensemble d'applications
for c,d in itertools.product(cat_source.objets, repeat=2):
ens_fleches = EnsFinis({frozenset(cat_source[{c},{d}]),frozenset(cat_cible({app_obj(c)},{app_obj(d)}))})
app_fleches_c_vers_d = ens_fleches({frozenset(cat_source[{c},{d}])},{frozenset(cat_cible({app_obj(c)},{app_obj(d)}))})
app_fleches_c_vers_d = set(ens_fleches({frozenset(cat_source[{c},{d}])},{frozenset(cat_cible({app_obj(c)},{app_obj(d)}))}))
app_fleche_foncteur[(c,d)] = app_fleches_c_vers_d
for applications_fleches in produit_cartesien_generateurs(*app_fleche_foncteur.values()):
if len(app_fleches_c_vers_d) == 0:
break
if len(app_fleche_foncteur[(c,d)]) == 0:
continue
for applications_fleches in itertools.product(*app_fleche_foncteur.values()):
app_fleches = dict()
for app in applications_fleches:
app_fleches.update(app.as_dict())
print(app_fleches)
for f in cat_source[abs(cat_source),abs(cat_source)]:
for g in cat_source[abs(cat_source),{f.source}]:
if f@g in app_fleches and app_fleches[f@g] != app_fleches[f]@app_fleches[g]:
......@@ -37,7 +38,6 @@ class CatFinies(Categorie):
continue
break
else:
print("yield")
yield Foncteur(cat_source,cat_cible,app_obj.as_dict(),app_fleches)
def test_CatFinies():
......
......@@ -132,8 +132,6 @@ class Categorie:
On peut surcharger la méthode __call__ de CategorieLibre pour optimiser les calculs quand c'est pertinent. Il faut alors
s'assurer que la nouvelle méthode renvoie les mêmes morphismes que l'ancienne méthode aurait renvoyée.
Il est aussi recommandé de surcharger decomposition_morphisme pour optimiser les calculs.
Si on surcharge __getitem__, on peut aussi surcharger __iter__ des morphismes associés à la catégorie pour itérer sur les constituants
élémentaires du morphisme.
"""
......@@ -141,16 +139,6 @@ class Categorie:
raise Exception("On s'attend à avoir un couple : "+str(couple_sources_cibles))
return self(*couple_sources_cibles)
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`.
Si __getitem__ n'a pas été surchargé, tous les morphismes sont des morphismes élémentaires.
On le renvoie donc simplement.
Si une classe fille surcharge __getitem__ il faut surcharger decomposition_morphisme aussi."""
if TOUJOURS_VERIFIER_COHERENCE_COMPOSEE:
if morphisme not in self[{morphisme.source},{morphisme.cible}]:
raise Exception("Le morphisme fourni n'est pas un morphisme elementaire et la methode decomposition_morphisme n'a pas ete surchargee : "+str(morphisme))
yield morphisme
def identite(self,objet: Any) -> Morphisme:
"""
......
......@@ -3,8 +3,6 @@ from Monoide import Monoide,ElementMonoideGC,MonoideGC
from Categorie import Categorie
from CategorieProduit import CategorieProduit
from CategorieLibre import CategorieLibre
from CatFinies import CatFinies
from Foncteur import Foncteur
import random
import copy
import itertools
......@@ -17,103 +15,55 @@ if PROGRESS_BAR:
from graphviz import Digraph
class GrapheCompositionAleatoire(GC):
"""Construit un graphe de composition aléatoire."""
def __init__(self, nb_fleches:Union[int,None] = None, nb_tentatives_complexification_loi_de_compo:Union[int,None] = None,nom:str = "Catégorie Aléatoire"):
def __init__(self, nb_fleches:Union[int,None] = None, nb_tentatives_complexification_loi_de_compo:int = 100,nom:str = "Catégorie Aléatoire"):
"""`nb_fleches` est le nombre de flèches élémentaires dans la catégorie aléatoire.
`nb_tentatives_complexification_loi_de_compo` détermine à quel point la loi de composition sera complexifiée,
si le nombre est faible on obtient une catégorie où les flèches sont isolées."""
if nb_fleches == None:
nb_fleches = random.randint(1,20)
nb_fleches = 10
if nb_tentatives_complexification_loi_de_compo == None:
nb_tentatives_complexification_loi_de_compo = random.randint(0,10*nb_fleches)
GC.__init__(self,nom=nom)
class LoiDeComposition:
def __init__(self):
self.table = defaultdict(lambda:None)
for i in range(1,2*nb_fleches+1):
for j in range(1,2*nb_fleches+1):
self.table[(-j,i)] = self.table[(i,-j)] = i # les identités sont neutres
self.table[(-j,-j)] = -j
# une classe par source et target de flèches initiales (soit 2*nb_fleches classes initiales)
self.obj_vers_fleche = defaultdict(set) # {numero_objet: {fleche1,fleche2,...}}
self.fleche_vers_obj = dict() # {fleche:numero_obj}
for fleche in self.fleches:
self.obj_vers_fleche[fleche] |= {fleche,self.identite_source_fleche(fleche)}
self.obj_vers_fleche[fleche+nb_fleches] |= {fleche,self.identite_cible_fleche(fleche)}
self.fleche_vers_obj[fleche] = fleche
self.fleche_vers_obj[fleche+nb_fleches] = fleche+nb_fleches
self.fleche_vers_obj[self.identite_source_fleche(fleche)] = fleche
self.fleche_vers_obj[self.identite_cible_fleche(fleche)] = fleche+nb_fleches
print(nb_fleches)
print(self.obj_vers_fleche)
print(self.fleche_vers_obj)
@property
def fleches(self) -> list:
'''Renvoie la liste des flèches.'''
return list(range(1,nb_fleches+1))
def source_fleche(self,i:int) -> int:
'''Renvoie la représentation de la source de la flèche i.'''
assert(i != 0)
return self.fleche_vers_obj[abs(i)]
def cible_fleche(self,i:int) -> int:
'''Renvoie la représentation de la cible de la flèche i.'''
assert(i != 0)
if i > 0:
return self.fleche_vers_obj[i+nb_fleches]
else:
return self.fleche_vers_obj[abs(i)]
def identite_source_fleche(self,i:int) -> int:
'''Renvoie la représentation de la flèche identité sur la source de la flèche i.'''
return -abs(i)
def identite_cible_fleche(self,i:int) -> int:
'''Renvoie la représentation de la flèche identité sur la cible de la flèche i.'''
return -((abs(i)+nb_fleches-1)%(2*nb_fleches)+1)
# la source d'une flèche est représentée par le numéro de la flèche
# la cible d'une flèche est représentée par le numero de la flèche plus le nombre de flèches
self.classe_equiv_vers_fleche = {i:{i} for i in range(nb_fleches*2)} # {numero_classe: {fleche1,fleche2,...}}
self.fleche_vers_classe_equiv = {i:i for i in range(nb_fleches*2)} # {fleche:numero_classe}
def linker_sources_fleches(self,fleche1,fleche2):
classe1,classe2 = self.fleche_vers_obj[self.source_fleche(fleche1)],self.fleche_vers_obj[self.source_fleche(fleche2)]
classe1,classe2 = self.fleche_vers_classe_equiv[fleche1],self.fleche_vers_classe_equiv[fleche2]
if classe1 != classe2:
#on fusionne les deux classes dans classe1
for fleche in self.obj_vers_fleche[classe2]:
self.fleche_vers_obj[self.source_fleche(fleche)] = classe1
self.fleche_vers_obj[self.source_fleche(self.identite_source_fleche(fleche))] = classe1
self.fleche_vers_obj[self.cible_fleche(self.identite_source_fleche(fleche))] = classe1
self.obj_vers_fleche[classe1] |= self.obj_vers_fleche[classe2]
del self.obj_vers_fleche[classe2]
for fleche in self.classe_equiv_vers_fleche[classe2]:
self.fleche_vers_classe_equiv[fleche] = classe1
self.classe_equiv_vers_fleche[classe1] |= self.classe_equiv_vers_fleche[classe2]
del self.classe_equiv_vers_fleche[classe2]
def linker_cibles_fleches(self,fleche1,fleche2):
classe1,classe2 = self.fleche_vers_obj[self.source_fleche(fleche1)],self.fleche_vers_obj[self.cible_fleche(fleche2)]
fleche1,fleche2 = fleche1+nb_fleches,fleche2+nb_fleches
classe1,classe2 = self.fleche_vers_classe_equiv[fleche1],self.fleche_vers_classe_equiv[fleche2]
if classe1 != classe2:
#on fusionne les deux classes dans classe1
for fleche in self.obj_vers_fleche[classe2]:
self.fleche_vers_obj[self.cible_fleche(fleche)] = classe1
self.fleche_vers_obj[self.source_fleche(self.identite_cible_fleche(fleche))] = classe1
self.fleche_vers_obj[self.cible_fleche(self.identite_cible_fleche(fleche))] = classe1
self.obj_vers_fleche[classe1] |= self.obj_vers_fleche[classe2]
del self.obj_vers_fleche[classe2]
for fleche in self.classe_equiv_vers_fleche[classe2]:
self.fleche_vers_classe_equiv[fleche] = classe1
self.classe_equiv_vers_fleche[classe1] |= self.classe_equiv_vers_fleche[classe2]
del self.classe_equiv_vers_fleche[classe2]
def linker_cible_source_fleches(self,fleche1,fleche2):
'''link la cible de `fleche1` à la source de `fleche2`'''
classe1,classe2 = self.fleche_vers_obj[self.cible_fleche(fleche1)],self.fleche_vers_obj[self.source_fleche(fleche2)]
fleche1 += nb_fleches
classe1,classe2 = self.fleche_vers_classe_equiv[fleche1],self.fleche_vers_classe_equiv[fleche2]
if classe1 != classe2:
#on fusionne les deux classes dans classe1
for fleche in self.obj_vers_fleche[classe2]:
self.fleche_vers_obj[fleche] = classe1
self.fleche_vers_obj[self.source_fleche(self.identite_source_fleche(fleche))] = classe1
self.fleche_vers_obj[self.cible_fleche(self.identite_source_fleche(fleche))] = classe1
self.obj_vers_fleche[classe1] |= self.obj_vers_fleche[classe2]
del self.obj_vers_fleche[classe2]
for fleche in self.classe_equiv_vers_fleche[classe2]:
self.fleche_vers_classe_equiv[fleche] = classe1
self.classe_equiv_vers_fleche[classe1] |= self.classe_equiv_vers_fleche[classe2]
del self.classe_equiv_vers_fleche[classe2]
def tentative_complexification(loi:LoiDeComposition,i:int,j:int,nb_tentatives:int = 3) -> Union[LoiDeComposition,None]:
"""Tente de complexifier la loi de composition en définissant la composition j o i.
......@@ -121,16 +71,9 @@ class GrapheCompositionAleatoire(GC):
nouvelle_loi = copy.deepcopy(loi)
if nouvelle_loi.table[(i,j)] != None:
return None
if nouvelle_loi.fleche_vers_obj[nouvelle_loi.source_fleche(i)] == nouvelle_loi.fleche_vers_obj[nouvelle_loi.cible_fleche(j)]:
# si la source de i et la cible de j sont dans la même classe d'équivalence
# une issue possible est j o i = Id
nouvelle_loi.table[(i,j)] = random.randint(0,nb_fleches)
if nouvelle_loi.table[(i,j)] == 0:
nouvelle_loi.table[(i,j)] = nouvelle_loi.identite_source_fleche(i)
else:
nouvelle_loi.table[(i,j)] = random.randint(1,nb_fleches)
nouvelle_loi.linker_sources_fleches(i,nouvelle_loi.table[(i,j)])
nouvelle_loi.linker_cibles_fleches(j,nouvelle_loi.table[(i,j)])
nouvelle_loi.table[(i,j)] = random.randint(0,nb_fleches-1)
nouvelle_loi.linker_sources_fleches(i,nouvelle_loi.table[(i,j)])
nouvelle_loi.linker_cibles_fleches(j,nouvelle_loi.table[(i,j)])
nouvelle_loi.linker_cible_source_fleches(i,j)
while True:
for a,b,c in itertools.product(range(nb_fleches),repeat=3):
......@@ -161,11 +104,11 @@ class GrapheCompositionAleatoire(GC):
probleme_composition = True
while probleme_composition:
probleme_composition = False
for obj in nouvelle_loi.obj_vers_fleche:
for fleche1 in nouvelle_loi.obj_vers_fleche[obj]:
if fleche1 > nb_fleches:
for fleche2 in nouvelle_loi.obj_vers_fleche[obj]:
if 0 < fleche2 < nb_fleches:
for classe_equiv in nouvelle_loi.classe_equiv_vers_fleche:
for fleche1 in nouvelle_loi.classe_equiv_vers_fleche[classe_equiv]:
if fleche1 >= nb_fleches:
for fleche2 in nouvelle_loi.classe_equiv_vers_fleche[classe_equiv]:
if fleche2 < nb_fleches:
# fleche1 et fleche2 sont censées être composables
if nouvelle_loi.table[(fleche1-nb_fleches,fleche2)] == None:
# problème de composition
......@@ -194,33 +137,29 @@ class GrapheCompositionAleatoire(GC):
else:
iterator = range(nb_tentatives_complexification_loi_de_compo)
for tentative in iterator:
i,j = random.randint(1,nb_fleches),random.randint(1,nb_fleches)
i,j = random.randint(0,nb_fleches-1),random.randint(0,nb_fleches-1)
loi2 = tentative_complexification(loi,i,j)
if loi2 != None:
loi = loi2
#on a une table de loi de composition aléatoire
for obj in loi.obj_vers_fleche:
self |= {obj}
for classe in loi.classe_equiv_vers_fleche:
self |= {classe}
#on va retirer les flèches qui peuvent être trouvées par composition de flèches élémentaires
fleches_composites = {loi.table[(i,j)] for i,j in itertools.product(loi.fleches,repeat=2) if loi.table[(i,j)] != None and loi.table[(i,j)] not in [i,j] and loi.table[(i,j)] > 0}
print(loi.fleches)
print(list(map(lambda x:str(loi.source_fleche(x)),loi.fleches)))
fleches = {i:MGC(loi.fleche_vers_obj[loi.source_fleche(i)],loi.fleche_vers_obj[loi.cible_fleche(i)]) for i in loi.fleches}
fleches_composites = {loi.table[(i,j)] for i,j in itertools.product(range(nb_fleches),repeat=2) if loi.table[(i,j)] not in [i,j] if loi.table[(i,j)] != None}
fleches = {i:MGC(loi.fleche_vers_classe_equiv[i],loi.fleche_vers_classe_equiv[i+nb_fleches]) for i in range(nb_fleches)}
self |= set(fleches.values())
self._fleches_elem = {fleches[i] for i in set(loi.fleches) - fleches_composites}
for i,j in itertools.product(loi.fleches,repeat=2):
self._fleches_elem = {fleches[i] for i in set(range(nb_fleches)) - fleches_composites}
for i,j in itertools.product(range(nb_fleches),repeat=2):
if loi.table[(i,j)] != None:
if loi.table[(i,j)] < 0:
MGC.identifier_morphismes(fleches[j]@fleches[i],self.identite(loi.fleche_vers_obj[loi.source_fleche(i)]))
elif fleches[loi.table[(i,j)]] != fleches[j]@fleches[i]:
if fleches[loi.table[(i,j)]] != fleches[j]@fleches[i]:
MGC.identifier_morphismes(fleches[j]@fleches[i],fleches[loi.table[(i,j)]])
with open("out.csv","w") as f:
f.write(","+",".join(map(str,range(-2*nb_fleches,nb_fleches+1)))+"\n")
for a in range(-2*nb_fleches,nb_fleches+1):
f.write(","+",".join(map(str,range(nb_fleches)))+"\n")
for a in range(nb_fleches):
f.write(str(a))
for b in range(-2*nb_fleches,nb_fleches+1):
for b in range(nb_fleches):
f.write(','+str(loi.table[(a,b)]))
f.write('\n')
......@@ -351,34 +290,13 @@ def test_MonoideGC():
mon.transformer_graphviz()
mon.loi_de_composition_to_csv(destination="lois de composition/monoide.csv")
class FoncteurAleatoire(Foncteur):
"""Foncteur aléatoire sur une catégorie."""
def __new__(cls, categorie_indexante:Categorie = GrapheCompositionAleatoire(), categorie_cible:Categorie = GrapheCompositionAleatoire(), nom:str = None):
if nom == None:
nom = "Foncteur aléatoire sur "+str(categorie_cible)
cat = CatFinies({categorie_indexante,categorie_cible}, "Catégorie des foncteurs pour génération de foncteur aléatoire")
foncteur = random.choice(list(cat({categorie_indexante},{categorie_cible})))
foncteur.nom = nom
return foncteur
# class FoncteurAleatoire(Foncteur):
# """Foncteur aléatoire sur une catégorie."""
# def __init__(self, categorie_indexante:Categorie = GrapheCompositionAleatoire(), categorie_cible:Categorie = GrapheCompositionAleatoire(), nom:str = None):
# def DiagrammeAleatoire(Diagramme):
# """Diagramme aléatoire sur une catégorie."""
# def __init__(self, categorie_indexante:Categorie = GrapheCompositionAleatoire(), categorie_cible:Categorie, nom:str = None):
# if nom == None:
# nom = "Foncteur aléatoire sur "+str(categorie_cible)
# cat = CatFinies({categorie_indexante,categorie_cible}, "Catégorie des foncteurs pour génération de foncteur aléatoire")
# foncteur = random.choice(cat({categorie_indexante},{categorie_cible}))
# Foncteur.__init__(self,categorie_indexante,categorie_cible,foncteur._app_obj,foncteur._app_morph,foncteur.is_identite)
def test_FoncteurAleatoire():
for i in range(20):
f = FoncteurAleatoire()
f.source.transformer_graphviz()
f.cible.transformer_graphviz()
f.transformer_graphviz()
# nom = "Diagramme aléatoire sur "+str(categorie_cible)
if __name__ == '__main__':
test_GrapheCompositionAleatoire()
# test_MonoideGC()
# test_FoncteurAleatoire()
\ No newline at end of file
test_MonoideGC()
\ No newline at end of file
......@@ -35,9 +35,6 @@ class CategorieLibre(Categorie):
"""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}.
Pour la catégorie libre, on doit énumérer tous les chemins et les composer.
Les classes filles peuvent surcharger cette méthode pour optimiser les calculs. Il faut alors s'assurer que les morphismes
générés sont les mêmes.
"""
for source in sources:
for cible in cibles:
......@@ -45,9 +42,7 @@ 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`.
Les classes filles peuvent surcharger cette méthode pour optimiser les calculs.'''
'''Renvoie un générateur de morphismes élémentaires qui composés donnent le `morphisme`.'''
if morphisme in self[{morphisme.source},{morphisme.cible}]:
yield morphisme
else:
......@@ -63,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
......
......@@ -75,7 +75,7 @@ class Application(Morphisme):
os.remove(destination)
class CategorieEnsemblesFinis(CategorieLibre):
class CategorieEnsemblesFinis(Categorie):
"""CategorieEnsFinis peut être abrégé en EnsFinis
Catégorie des ensembles finis, cette catégorie est infinie, on ajoute uniquement les ensembles dont on a besoin.
/!\ __call__ n'appelle pas __getitem__ /!\ """
......@@ -211,7 +211,7 @@ def test_EnsFinis():
def test_EnsParties():
cat = EnsParties({1,2,3})
cat.transformer_graphviz(complet=True)
cat.transformer_graphviz(complet=False)
for app in cat({frozenset({1,2,3})},{frozenset({1,2})}):
app.transformer_graphviz()
......@@ -224,5 +224,5 @@ def test_CategorieBijections():
if __name__ == '__main__':
test_EnsFinis()
test_EnsParties()
test_CategorieBijections()
\ No newline at end of file
# test_EnsParties()
# test_CategorieBijections()
\ No newline at end of file
......@@ -94,15 +94,12 @@ class Foncteur(Morphisme):
str(self(g@f))+' != '+str(self(g)@self(f)))
def __call__(self,param:Any)->Any:
'''Applique le foncteur à un objet ou un morphisme de la catégorie source.
Si le morphisme $h = g o f$ avec g et f des morphismes élémentaires, préferer calculer foncteur(g) o foncteur(f)
plutôt que foncteur(h) (évite l'appel à la fonction decomposition_morphisme).'''
'''Applique le foncteur à un objet ou un morphisme de la catégorie source.'''
if param in self._app_objets:
return self._app_objets[param]
if param in self._app_morph:
return self._app_morph[param]
decompo = self.source.decomposition_morphisme(param)
decompo = self.source.decomposition_morphisme(param)
return functools.reduce(lambda x,y:x@y,[self._app_morph[morph] for morph in decompo])
def transformer_graphviz(self, destination:Union[str,None]=None, afficher_identites:bool = False):
......
......@@ -147,17 +147,6 @@ class GrapheDeComposition(CategorieLibre):
for cible in cibles:
for fleche in self.morph_sortants(source)&self.morph_entrants(cible):
yield fleche
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`.
Les classes filles peuvent surcharger cette méthode pour optimiser les calculs.'''
for m in morphisme:
if m in self[{m.source},{m.cible}]:
yield m
else:
for morph in CategorieLibre.decomposition_morphisme(self,m):
yield morph
def __eq__(self,other:'GrapheDeComposition') -> bool:
return type(other) == type(self) and self.__identites == other.__identites and\
......
......@@ -6,7 +6,4 @@ GRAPHVIZ_CONCENTRATE_GRAPHS = False
WARNING_LIMITE_FLECHES_ATTEINTE = True
PRINT_AVANCEMENT_CREATION_CAT_ALEA = True
CLEAN_GRAPHVIZ_MODEL = True
PROGRESS_BAR = True
import random
random.seed(1)
\ No newline at end of file
PROGRESS_BAR = True
\ No newline at end of file
......@@ -59,16 +59,4 @@ 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)
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
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