Skip to content

Commit cc3da36

Browse files
author
Release Manager
committed
gh-35097: Simplicial set group <!-- ^^^^^ Please provide a concise, informative and self-explanatory title. Don't put issue numbers in there, do this in the PR body below. For example, instead of "Fixes #1234" use "Introduce new method to calculate 1+1" --> ### 📚 Implementation of finite covers of simplicial sets <!-- Describe your changes here in detail --> <!-- Why is this change required? What problem does it solve? --> <!-- If it resolves an open issue, please link to the issue here. For example "Closes #1337" --> Compute the cover of a (based) simplicial set associated to a representation of its fundamental group to a finite group. Fixes #34886. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> <!-- If your change requires a documentation PR, please link it appropriately --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [x] I have made sure that the title is self-explanatory and the description concisely explains the PR. - [ ] I have linked an issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open pull requests that this PR logically depends on --> <!-- - #xyz: short description why this is a dependency - #abc: ... --> URL: #35097 Reported by: miguelmarco Reviewer(s): John H. Palmieri
2 parents b0728b0 + 8a7729c commit cc3da36

File tree

4 files changed

+286
-34
lines changed

4 files changed

+286
-34
lines changed

src/sage/categories/simplicial_sets.py

+210-12
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ def fundamental_group(self, simplify=True):
317317
sage: Sigma3 = groups.permutation.Symmetric(3)
318318
sage: BSigma3 = Sigma3.nerve()
319319
sage: pi = BSigma3.fundamental_group(); pi
320-
Finitely presented group < e0, e1 | e0^2, e1^3, (e0*e1^-1)^2 >
320+
Finitely presented group < e1, e2 | e2^2, e1^3, (e2*e1)^2 >
321321
sage: pi.order()
322322
6
323323
sage: pi.is_abelian()
@@ -331,19 +331,35 @@ def fundamental_group(self, simplify=True):
331331
"""
332332
# Import this here to prevent importing libgap upon startup.
333333
from sage.groups.free_group import FreeGroup
334-
skel = self.n_skeleton(2)
334+
if not self.n_cells(1):
335+
return FreeGroup([]).quotient([])
336+
FG = self._universal_cover_dict()[0]
337+
if simplify:
338+
return FG.simplified()
339+
else:
340+
return FG
335341

342+
def _universal_cover_dict(self):
343+
r"""
344+
Return the fundamental group and dictionary sending each edge to
345+
the corresponding group element
346+
347+
TESTS::
348+
349+
sage: RP2 = simplicial_sets.RealProjectiveSpace(2)
350+
sage: RP2._universal_cover_dict()
351+
(Finitely presented group < e | e^2 >, {f: e})
352+
sage: RP2.nondegenerate_simplices()
353+
[1, f, f * f]
354+
"""
355+
from sage.groups.free_group import FreeGroup
356+
skel = self.n_skeleton(2)
336357
graph = skel.graph()
337358
if not skel.is_connected():
338359
graph = graph.subgraph(skel.base_point())
339-
340-
edges = [e[2] for e in graph.edges(sort=True)]
360+
edges = [e[2] for e in graph.edges(sort=False)]
341361
spanning_tree = [e[2] for e in graph.min_spanning_tree()]
342362
gens = [e for e in edges if e not in spanning_tree]
343-
344-
if not gens:
345-
return FreeGroup([]).quotient([])
346-
347363
gens_dict = dict(zip(gens, range(len(gens))))
348364
FG = FreeGroup(len(gens), 'e')
349365
rels = []
@@ -361,10 +377,192 @@ def fundamental_group(self, simplify=True):
361377
# sigma is not in the correct connected component.
362378
z[i] = FG.one()
363379
rels.append(z[0]*z[1].inverse()*z[2])
364-
if simplify:
365-
return FG.quotient(rels).simplified()
366-
else:
367-
return FG.quotient(rels)
380+
G = FG.quotient(rels)
381+
char = {g : G.gen(i) for i,g in enumerate(gens)}
382+
for e in edges:
383+
if e not in gens:
384+
char[e] = G.one()
385+
return (G, char)
386+
387+
388+
def universal_cover_map(self):
389+
r"""
390+
Return the universal covering map of the simplicial set.
391+
392+
It requires the fundamental group to be finite.
393+
394+
EXAMPLES::
395+
396+
sage: RP2 = simplicial_sets.RealProjectiveSpace(2)
397+
sage: phi = RP2.universal_cover_map()
398+
sage: phi
399+
Simplicial set morphism:
400+
From: Simplicial set with 6 non-degenerate simplices
401+
To: RP^2
402+
Defn: [(1, 1), (1, e), (f, 1), (f, e), (f * f, 1), (f * f, e)] --> [1, 1, f, f, f * f, f * f]
403+
sage: phi.domain().face_data()
404+
{(1, 1): None,
405+
(1, e): None,
406+
(f, 1): ((1, e), (1, 1)),
407+
(f, e): ((1, 1), (1, e)),
408+
(f * f, 1): ((f, e), s_0 (1, 1), (f, 1)),
409+
(f * f, e): ((f, 1), s_0 (1, e), (f, e))}
410+
411+
"""
412+
edges = self.n_cells(1)
413+
if not edges:
414+
return self.identity()
415+
G, char = self._universal_cover_dict()
416+
return self.covering_map(char)
417+
418+
def covering_map(self, character):
419+
r"""
420+
Return the covering map associated to a character.
421+
422+
The character is represented by a dictionary that assigns an
423+
element of a finite group to each nondegenerate 1-dimensional
424+
cell. It should correspond to an epimorphism from the fundamental
425+
group.
426+
427+
INPUT:
428+
429+
- ``character`` -- a dictionary
430+
431+
432+
EXAMPLES::
433+
434+
sage: S1 = simplicial_sets.Sphere(1)
435+
sage: W = S1.wedge(S1)
436+
sage: G = CyclicPermutationGroup(3)
437+
sage: a, b = W.n_cells(1)
438+
sage: C = W.covering_map({a : G.gen(0), b : G.one()})
439+
sage: C
440+
Simplicial set morphism:
441+
From: Simplicial set with 9 non-degenerate simplices
442+
To: Wedge: (S^1 v S^1)
443+
Defn: [(*, ()), (*, (1,2,3)), (*, (1,3,2)), (sigma_1, ()), (sigma_1, ()), (sigma_1, (1,2,3)), (sigma_1, (1,2,3)), (sigma_1, (1,3,2)), (sigma_1, (1,3,2))] --> [*, *, *, sigma_1, sigma_1, sigma_1, sigma_1, sigma_1, sigma_1]
444+
sage: C.domain()
445+
Simplicial set with 9 non-degenerate simplices
446+
sage: C.domain().face_data()
447+
{(*, ()): None,
448+
(*, (1,2,3)): None,
449+
(*, (1,3,2)): None,
450+
(sigma_1, ()): ((*, (1,2,3)), (*, ())),
451+
(sigma_1, ()): ((*, ()), (*, ())),
452+
(sigma_1, (1,2,3)): ((*, (1,3,2)), (*, (1,2,3))),
453+
(sigma_1, (1,2,3)): ((*, (1,2,3)), (*, (1,2,3))),
454+
(sigma_1, (1,3,2)): ((*, ()), (*, (1,3,2))),
455+
(sigma_1, (1,3,2)): ((*, (1,3,2)), (*, (1,3,2)))}
456+
"""
457+
from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet
458+
from sage.topology.simplicial_set_morphism import SimplicialSetMorphism
459+
char = {a : b for (a,b) in character.items()}
460+
G = list(char.values())[0].parent()
461+
if not G.is_finite():
462+
raise NotImplementedError("can only compute universal covers of spaces with finite fundamental group")
463+
cells_dict = {}
464+
faces_dict = {}
465+
466+
for s in self.n_cells(0):
467+
for g in G:
468+
cell = AbstractSimplex(0,name="({}, {})".format(s, g))
469+
cells_dict[(s,g)] = cell
470+
char[s] = G.one()
471+
472+
for d in range(1, self.dimension() + 1):
473+
for s in self.n_cells(d):
474+
if s not in char.keys():
475+
if d==1 and s.is_degenerate():
476+
char[s] = G.one()
477+
elif s.is_degenerate():
478+
if 0 in s.degeneracies():
479+
char[s] = G.one()
480+
else:
481+
char[s] = char[s.nondegenerate()]
482+
else:
483+
char[s] = char[self.face(s, d)]
484+
if s.is_nondegenerate():
485+
for g in G:
486+
cell = AbstractSimplex(d,name="({}, {})".format(s, g))
487+
cells_dict[(s,g)] = cell
488+
fd = []
489+
faces = self.faces(s)
490+
f0 = faces[0]
491+
for h in G:
492+
if h == g*char[s]:
493+
lifted = h
494+
break
495+
grelems = [cells_dict[(f0.nondegenerate(), lifted)].apply_degeneracies(*f0.degeneracies())]
496+
for f in faces[1:]:
497+
grelems.append(cells_dict[(f.nondegenerate(), g)].apply_degeneracies(*f.degeneracies()))
498+
faces_dict[cell] = grelems
499+
cover = SimplicialSet(faces_dict, base_point=cells_dict[(self.base_point(), G.one())])
500+
cover_map_data = {c : s[0] for (s,c) in cells_dict.items()}
501+
return SimplicialSetMorphism(data = cover_map_data, domain = cover, codomain = self)
502+
503+
def cover(self, character):
504+
r"""
505+
Return the cover of the simplicial set associated to a character
506+
of the fundamental group.
507+
508+
The character is represented by a dictionary, that assigns an
509+
element of a finite group to each nondegenerate 1-dimensional
510+
cell. It should correspond to an epimorphism from the fundamental
511+
group.
512+
513+
INPUT:
514+
515+
- ``character`` -- a dictionary
516+
517+
EXAMPLES::
518+
519+
sage: S1 = simplicial_sets.Sphere(1)
520+
sage: W = S1.wedge(S1)
521+
sage: G = CyclicPermutationGroup(3)
522+
sage: (a, b) = W.n_cells(1)
523+
sage: C = W.cover({a : G.gen(0), b : G.gen(0)^2})
524+
sage: C.face_data()
525+
{(*, ()): None,
526+
(*, (1,2,3)): None,
527+
(*, (1,3,2)): None,
528+
(sigma_1, ()): ((*, (1,2,3)), (*, ())),
529+
(sigma_1, ()): ((*, (1,3,2)), (*, ())),
530+
(sigma_1, (1,2,3)): ((*, (1,3,2)), (*, (1,2,3))),
531+
(sigma_1, (1,2,3)): ((*, ()), (*, (1,2,3))),
532+
(sigma_1, (1,3,2)): ((*, ()), (*, (1,3,2))),
533+
(sigma_1, (1,3,2)): ((*, (1,2,3)), (*, (1,3,2)))}
534+
sage: C.homology(1)
535+
Z x Z x Z x Z
536+
sage: C.fundamental_group()
537+
Finitely presented group < e0, e1, e2, e3 | >
538+
"""
539+
return self.covering_map(character).domain()
540+
541+
def universal_cover(self):
542+
r"""
543+
Return the universal cover of the simplicial set.
544+
The fundamental group must be finite in order to ensure that the
545+
universal cover is a simplicial set of finite type.
546+
547+
EXAMPLES::
548+
549+
sage: RP3 = simplicial_sets.RealProjectiveSpace(3)
550+
sage: C = RP3.universal_cover()
551+
sage: C
552+
Simplicial set with 8 non-degenerate simplices
553+
sage: C.face_data()
554+
{(1, 1): None,
555+
(1, e): None,
556+
(f, 1): ((1, e), (1, 1)),
557+
(f, e): ((1, 1), (1, e)),
558+
(f * f, 1): ((f, e), s_0 (1, 1), (f, 1)),
559+
(f * f, e): ((f, 1), s_0 (1, e), (f, e)),
560+
(f * f * f, 1): ((f * f, e), s_0 (f, 1), s_1 (f, 1), (f * f, 1)),
561+
(f * f * f, e): ((f * f, 1), s_0 (f, e), s_1 (f, e), (f * f, e))}
562+
sage: C.fundamental_group()
563+
Finitely presented group < | >
564+
"""
565+
return self.universal_cover_map().domain()
368566

369567
def is_simply_connected(self):
370568
"""

src/sage/topology/simplicial_set.py

+7-21
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@
173173
sage: Sigma3 = groups.permutation.Symmetric(3)
174174
sage: BSigma3 = Sigma3.nerve()
175175
sage: pi = BSigma3.fundamental_group(); pi
176-
Finitely presented group < e0, e1 | e0^2, e1^3, (e0*e1^-1)^2 >
176+
Finitely presented group < e1, e2 | e2^2, e1^3, (e2*e1)^2 >
177177
sage: pi.order()
178178
6
179179
sage: pi.is_abelian()
@@ -1686,26 +1686,12 @@ def graph(self):
16861686
sage: Sigma3.nerve().is_connected()
16871687
True
16881688
"""
1689-
skel = self.n_skeleton(1)
1690-
edges = skel.n_cells(1)
1691-
vertices = skel.n_cells(0)
1692-
used_vertices = set() # vertices which are in an edge
1693-
d = {}
1694-
for e in edges:
1695-
v = skel.face(e, 0)
1696-
w = skel.face(e, 1)
1697-
if v in d:
1698-
if w in d[v]:
1699-
d[v][w] = d[v][w] + [e]
1700-
else:
1701-
d[v][w] = [e]
1702-
else:
1703-
d[v] = {w: [e]}
1704-
used_vertices.update([v, w])
1705-
for v in vertices:
1706-
if v not in used_vertices:
1707-
d[v] = {}
1708-
return Graph(d, format='dict_of_dicts')
1689+
G = Graph(loops=True, multiedges=True)
1690+
for e in self.n_cells(1):
1691+
G.add_edge(self.face(e,0), self.face(e,1), e)
1692+
for v in self.n_cells(0):
1693+
G.add_vertex(v)
1694+
return G
17091695

17101696
def is_connected(self):
17111697
"""

src/sage/topology/simplicial_set_catalog.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
- the Hopf map: this is a pre-built morphism, from which one can
2727
extract its domain, codomain, mapping cone, etc.
2828
29+
- the complex of a group presentation.
30+
2931
All of these examples are accessible by typing
3032
``simplicial_sets.NAME``, where ``NAME`` is the name of the
3133
example. Type ``simplicial_sets.[TAB]`` for a complete list.
@@ -48,4 +50,4 @@
4850
KleinBottle, Torus,
4951
Simplex, Horn, Point,
5052
ComplexProjectiveSpace,
51-
HopfMap)
53+
HopfMap, PresentationComplex)

src/sage/topology/simplicial_set_examples.py

+66
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
AUTHORS:
1010
1111
- John H. Palmieri (2016-07)
12+
13+
- Miguel Marco (2022-12)
1214
"""
1315
# ****************************************************************************
1416
# Copyright (C) 2016 John H. Palmieri <palmieri at math.washington.edu>
@@ -48,6 +50,8 @@
4850
from sage.misc.lazy_import import lazy_import
4951
lazy_import('sage.categories.simplicial_sets', 'SimplicialSets')
5052

53+
54+
5155
########################################################################
5256
# The nerve of a finite monoid, used in sage.categories.finite_monoid.
5357

@@ -790,3 +794,65 @@ def HopfMap():
790794
return S3.Hom(S2)({alpha_1:s0_sigma, alpha_2:s1_sigma,
791795
alpha_3:s2_sigma, alpha_4:s0_sigma,
792796
alpha_5:s2_sigma, alpha_6:s1_sigma})
797+
798+
799+
def PresentationComplex(G):
800+
r"""
801+
Return a simplicial set constructed from a group presentation.
802+
The result is a subdivision of the presentation complex.
803+
804+
The presentation complex has a single vertex and it has one edge for
805+
each generator. Then triangles (and eventually new edges
806+
to glue them) are added to realize the relations.
807+
808+
INPUT:
809+
810+
- "G" -- a finitely presented group
811+
812+
EXAMPLES::
813+
814+
sage: G = SymmetricGroup(2).as_finitely_presented_group()
815+
sage: G
816+
Finitely presented group < a | a^2 >
817+
sage: S = simplicial_sets.PresentationComplex(G)
818+
sage: S
819+
Simplicial set with 5 non-degenerate simplices
820+
sage: S.face_data()
821+
{Delta^0: None,
822+
a: (Delta^0, Delta^0),
823+
a^-1: (Delta^0, Delta^0),
824+
Ta: (a, s_0 Delta^0, a^-1),
825+
a^2: (a, s_0 Delta^0, a)}
826+
sage: S.fundamental_group()
827+
Finitely presented group < e0 | e0^2 >
828+
"""
829+
O = AbstractSimplex(0)
830+
SO = O.apply_degeneracies(0)
831+
edges = {g: AbstractSimplex(1, name=str(g)) for g in G.gens()}
832+
inverseedges = {g.inverse(): AbstractSimplex(1, name=str(g.inverse())) for g in G.gens()}
833+
all_edges = {}
834+
all_edges.update(edges)
835+
all_edges.update(inverseedges)
836+
triangles = {g: AbstractSimplex(2, name='T' + str(g)) for g in G.gens()}
837+
face_maps = {g: [O, O] for g in all_edges.values()}
838+
face_maps.update({triangles[t]: [all_edges[t], SO, all_edges[t.inverse()]] for t in triangles})
839+
for r in G.relations():
840+
if len(r.Tietze()) == 1:
841+
pass
842+
elif len(r.Tietze()) == 2:
843+
a = all_edges[G([r.Tietze()[0]])]
844+
b = all_edges[G([r.Tietze()[1]])]
845+
T = AbstractSimplex(2, name=str(r))
846+
face_maps[T] = [a, SO, b]
847+
else:
848+
words = [all_edges[G([a])] for a in r.Tietze()]
849+
words[-1] = all_edges[G([-r.Tietze()[-1]])]
850+
while len(words) > 3:
851+
auxedge = AbstractSimplex(1)
852+
face_maps[auxedge] = [O, O]
853+
auxtring = AbstractSimplex(2)
854+
face_maps[auxtring] = [words[1], auxedge, words[0]]
855+
words = [auxedge] + words[2:]
856+
auxtring = AbstractSimplex(2)
857+
face_maps[auxtring] = [words[1], words[2], words[0]]
858+
return SimplicialSet_finite(face_maps, base_point=O)

0 commit comments

Comments
 (0)