Skip to content

Commit

Permalink
gh-35097: Simplicial set group
Browse files Browse the repository at this point in the history
    
<!-- ^^^^^
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
  • Loading branch information
Release Manager committed Mar 18, 2023
2 parents b0728b0 + 8a7729c commit cc3da36
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 34 deletions.
222 changes: 210 additions & 12 deletions src/sage/categories/simplicial_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def fundamental_group(self, simplify=True):
sage: Sigma3 = groups.permutation.Symmetric(3)
sage: BSigma3 = Sigma3.nerve()
sage: pi = BSigma3.fundamental_group(); pi
Finitely presented group < e0, e1 | e0^2, e1^3, (e0*e1^-1)^2 >
Finitely presented group < e1, e2 | e2^2, e1^3, (e2*e1)^2 >
sage: pi.order()
6
sage: pi.is_abelian()
Expand All @@ -331,19 +331,35 @@ def fundamental_group(self, simplify=True):
"""
# Import this here to prevent importing libgap upon startup.
from sage.groups.free_group import FreeGroup
skel = self.n_skeleton(2)
if not self.n_cells(1):
return FreeGroup([]).quotient([])
FG = self._universal_cover_dict()[0]
if simplify:
return FG.simplified()
else:
return FG

def _universal_cover_dict(self):
r"""
Return the fundamental group and dictionary sending each edge to
the corresponding group element
TESTS::
sage: RP2 = simplicial_sets.RealProjectiveSpace(2)
sage: RP2._universal_cover_dict()
(Finitely presented group < e | e^2 >, {f: e})
sage: RP2.nondegenerate_simplices()
[1, f, f * f]
"""
from sage.groups.free_group import FreeGroup
skel = self.n_skeleton(2)
graph = skel.graph()
if not skel.is_connected():
graph = graph.subgraph(skel.base_point())

edges = [e[2] for e in graph.edges(sort=True)]
edges = [e[2] for e in graph.edges(sort=False)]
spanning_tree = [e[2] for e in graph.min_spanning_tree()]
gens = [e for e in edges if e not in spanning_tree]

if not gens:
return FreeGroup([]).quotient([])

gens_dict = dict(zip(gens, range(len(gens))))
FG = FreeGroup(len(gens), 'e')
rels = []
Expand All @@ -361,10 +377,192 @@ def fundamental_group(self, simplify=True):
# sigma is not in the correct connected component.
z[i] = FG.one()
rels.append(z[0]*z[1].inverse()*z[2])
if simplify:
return FG.quotient(rels).simplified()
else:
return FG.quotient(rels)
G = FG.quotient(rels)
char = {g : G.gen(i) for i,g in enumerate(gens)}
for e in edges:
if e not in gens:
char[e] = G.one()
return (G, char)


def universal_cover_map(self):
r"""
Return the universal covering map of the simplicial set.
It requires the fundamental group to be finite.
EXAMPLES::
sage: RP2 = simplicial_sets.RealProjectiveSpace(2)
sage: phi = RP2.universal_cover_map()
sage: phi
Simplicial set morphism:
From: Simplicial set with 6 non-degenerate simplices
To: RP^2
Defn: [(1, 1), (1, e), (f, 1), (f, e), (f * f, 1), (f * f, e)] --> [1, 1, f, f, f * f, f * f]
sage: phi.domain().face_data()
{(1, 1): None,
(1, e): None,
(f, 1): ((1, e), (1, 1)),
(f, e): ((1, 1), (1, e)),
(f * f, 1): ((f, e), s_0 (1, 1), (f, 1)),
(f * f, e): ((f, 1), s_0 (1, e), (f, e))}
"""
edges = self.n_cells(1)
if not edges:
return self.identity()
G, char = self._universal_cover_dict()
return self.covering_map(char)

def covering_map(self, character):
r"""
Return the covering map associated to a character.
The character is represented by a dictionary that assigns an
element of a finite group to each nondegenerate 1-dimensional
cell. It should correspond to an epimorphism from the fundamental
group.
INPUT:
- ``character`` -- a dictionary
EXAMPLES::
sage: S1 = simplicial_sets.Sphere(1)
sage: W = S1.wedge(S1)
sage: G = CyclicPermutationGroup(3)
sage: a, b = W.n_cells(1)
sage: C = W.covering_map({a : G.gen(0), b : G.one()})
sage: C
Simplicial set morphism:
From: Simplicial set with 9 non-degenerate simplices
To: Wedge: (S^1 v S^1)
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]
sage: C.domain()
Simplicial set with 9 non-degenerate simplices
sage: C.domain().face_data()
{(*, ()): None,
(*, (1,2,3)): None,
(*, (1,3,2)): None,
(sigma_1, ()): ((*, (1,2,3)), (*, ())),
(sigma_1, ()): ((*, ()), (*, ())),
(sigma_1, (1,2,3)): ((*, (1,3,2)), (*, (1,2,3))),
(sigma_1, (1,2,3)): ((*, (1,2,3)), (*, (1,2,3))),
(sigma_1, (1,3,2)): ((*, ()), (*, (1,3,2))),
(sigma_1, (1,3,2)): ((*, (1,3,2)), (*, (1,3,2)))}
"""
from sage.topology.simplicial_set import AbstractSimplex, SimplicialSet
from sage.topology.simplicial_set_morphism import SimplicialSetMorphism
char = {a : b for (a,b) in character.items()}
G = list(char.values())[0].parent()
if not G.is_finite():
raise NotImplementedError("can only compute universal covers of spaces with finite fundamental group")
cells_dict = {}
faces_dict = {}

for s in self.n_cells(0):
for g in G:
cell = AbstractSimplex(0,name="({}, {})".format(s, g))
cells_dict[(s,g)] = cell
char[s] = G.one()

for d in range(1, self.dimension() + 1):
for s in self.n_cells(d):
if s not in char.keys():
if d==1 and s.is_degenerate():
char[s] = G.one()
elif s.is_degenerate():
if 0 in s.degeneracies():
char[s] = G.one()
else:
char[s] = char[s.nondegenerate()]
else:
char[s] = char[self.face(s, d)]
if s.is_nondegenerate():
for g in G:
cell = AbstractSimplex(d,name="({}, {})".format(s, g))
cells_dict[(s,g)] = cell
fd = []
faces = self.faces(s)
f0 = faces[0]
for h in G:
if h == g*char[s]:
lifted = h
break
grelems = [cells_dict[(f0.nondegenerate(), lifted)].apply_degeneracies(*f0.degeneracies())]
for f in faces[1:]:
grelems.append(cells_dict[(f.nondegenerate(), g)].apply_degeneracies(*f.degeneracies()))
faces_dict[cell] = grelems
cover = SimplicialSet(faces_dict, base_point=cells_dict[(self.base_point(), G.one())])
cover_map_data = {c : s[0] for (s,c) in cells_dict.items()}
return SimplicialSetMorphism(data = cover_map_data, domain = cover, codomain = self)

def cover(self, character):
r"""
Return the cover of the simplicial set associated to a character
of the fundamental group.
The character is represented by a dictionary, that assigns an
element of a finite group to each nondegenerate 1-dimensional
cell. It should correspond to an epimorphism from the fundamental
group.
INPUT:
- ``character`` -- a dictionary
EXAMPLES::
sage: S1 = simplicial_sets.Sphere(1)
sage: W = S1.wedge(S1)
sage: G = CyclicPermutationGroup(3)
sage: (a, b) = W.n_cells(1)
sage: C = W.cover({a : G.gen(0), b : G.gen(0)^2})
sage: C.face_data()
{(*, ()): None,
(*, (1,2,3)): None,
(*, (1,3,2)): None,
(sigma_1, ()): ((*, (1,2,3)), (*, ())),
(sigma_1, ()): ((*, (1,3,2)), (*, ())),
(sigma_1, (1,2,3)): ((*, (1,3,2)), (*, (1,2,3))),
(sigma_1, (1,2,3)): ((*, ()), (*, (1,2,3))),
(sigma_1, (1,3,2)): ((*, ()), (*, (1,3,2))),
(sigma_1, (1,3,2)): ((*, (1,2,3)), (*, (1,3,2)))}
sage: C.homology(1)
Z x Z x Z x Z
sage: C.fundamental_group()
Finitely presented group < e0, e1, e2, e3 | >
"""
return self.covering_map(character).domain()

def universal_cover(self):
r"""
Return the universal cover of the simplicial set.
The fundamental group must be finite in order to ensure that the
universal cover is a simplicial set of finite type.
EXAMPLES::
sage: RP3 = simplicial_sets.RealProjectiveSpace(3)
sage: C = RP3.universal_cover()
sage: C
Simplicial set with 8 non-degenerate simplices
sage: C.face_data()
{(1, 1): None,
(1, e): None,
(f, 1): ((1, e), (1, 1)),
(f, e): ((1, 1), (1, e)),
(f * f, 1): ((f, e), s_0 (1, 1), (f, 1)),
(f * f, e): ((f, 1), s_0 (1, e), (f, e)),
(f * f * f, 1): ((f * f, e), s_0 (f, 1), s_1 (f, 1), (f * f, 1)),
(f * f * f, e): ((f * f, 1), s_0 (f, e), s_1 (f, e), (f * f, e))}
sage: C.fundamental_group()
Finitely presented group < | >
"""
return self.universal_cover_map().domain()

def is_simply_connected(self):
"""
Expand Down
28 changes: 7 additions & 21 deletions src/sage/topology/simplicial_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
sage: Sigma3 = groups.permutation.Symmetric(3)
sage: BSigma3 = Sigma3.nerve()
sage: pi = BSigma3.fundamental_group(); pi
Finitely presented group < e0, e1 | e0^2, e1^3, (e0*e1^-1)^2 >
Finitely presented group < e1, e2 | e2^2, e1^3, (e2*e1)^2 >
sage: pi.order()
6
sage: pi.is_abelian()
Expand Down Expand Up @@ -1686,26 +1686,12 @@ def graph(self):
sage: Sigma3.nerve().is_connected()
True
"""
skel = self.n_skeleton(1)
edges = skel.n_cells(1)
vertices = skel.n_cells(0)
used_vertices = set() # vertices which are in an edge
d = {}
for e in edges:
v = skel.face(e, 0)
w = skel.face(e, 1)
if v in d:
if w in d[v]:
d[v][w] = d[v][w] + [e]
else:
d[v][w] = [e]
else:
d[v] = {w: [e]}
used_vertices.update([v, w])
for v in vertices:
if v not in used_vertices:
d[v] = {}
return Graph(d, format='dict_of_dicts')
G = Graph(loops=True, multiedges=True)
for e in self.n_cells(1):
G.add_edge(self.face(e,0), self.face(e,1), e)
for v in self.n_cells(0):
G.add_vertex(v)
return G

def is_connected(self):
"""
Expand Down
4 changes: 3 additions & 1 deletion src/sage/topology/simplicial_set_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
- the Hopf map: this is a pre-built morphism, from which one can
extract its domain, codomain, mapping cone, etc.
- the complex of a group presentation.
All of these examples are accessible by typing
``simplicial_sets.NAME``, where ``NAME`` is the name of the
example. Type ``simplicial_sets.[TAB]`` for a complete list.
Expand All @@ -48,4 +50,4 @@
KleinBottle, Torus,
Simplex, Horn, Point,
ComplexProjectiveSpace,
HopfMap)
HopfMap, PresentationComplex)
66 changes: 66 additions & 0 deletions src/sage/topology/simplicial_set_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
AUTHORS:
- John H. Palmieri (2016-07)
- Miguel Marco (2022-12)
"""
# ****************************************************************************
# Copyright (C) 2016 John H. Palmieri <palmieri at math.washington.edu>
Expand Down Expand Up @@ -48,6 +50,8 @@
from sage.misc.lazy_import import lazy_import
lazy_import('sage.categories.simplicial_sets', 'SimplicialSets')



########################################################################
# The nerve of a finite monoid, used in sage.categories.finite_monoid.

Expand Down Expand Up @@ -790,3 +794,65 @@ def HopfMap():
return S3.Hom(S2)({alpha_1:s0_sigma, alpha_2:s1_sigma,
alpha_3:s2_sigma, alpha_4:s0_sigma,
alpha_5:s2_sigma, alpha_6:s1_sigma})


def PresentationComplex(G):
r"""
Return a simplicial set constructed from a group presentation.
The result is a subdivision of the presentation complex.
The presentation complex has a single vertex and it has one edge for
each generator. Then triangles (and eventually new edges
to glue them) are added to realize the relations.
INPUT:
- "G" -- a finitely presented group
EXAMPLES::
sage: G = SymmetricGroup(2).as_finitely_presented_group()
sage: G
Finitely presented group < a | a^2 >
sage: S = simplicial_sets.PresentationComplex(G)
sage: S
Simplicial set with 5 non-degenerate simplices
sage: S.face_data()
{Delta^0: None,
a: (Delta^0, Delta^0),
a^-1: (Delta^0, Delta^0),
Ta: (a, s_0 Delta^0, a^-1),
a^2: (a, s_0 Delta^0, a)}
sage: S.fundamental_group()
Finitely presented group < e0 | e0^2 >
"""
O = AbstractSimplex(0)
SO = O.apply_degeneracies(0)
edges = {g: AbstractSimplex(1, name=str(g)) for g in G.gens()}
inverseedges = {g.inverse(): AbstractSimplex(1, name=str(g.inverse())) for g in G.gens()}
all_edges = {}
all_edges.update(edges)
all_edges.update(inverseedges)
triangles = {g: AbstractSimplex(2, name='T' + str(g)) for g in G.gens()}
face_maps = {g: [O, O] for g in all_edges.values()}
face_maps.update({triangles[t]: [all_edges[t], SO, all_edges[t.inverse()]] for t in triangles})
for r in G.relations():
if len(r.Tietze()) == 1:
pass
elif len(r.Tietze()) == 2:
a = all_edges[G([r.Tietze()[0]])]
b = all_edges[G([r.Tietze()[1]])]
T = AbstractSimplex(2, name=str(r))
face_maps[T] = [a, SO, b]
else:
words = [all_edges[G([a])] for a in r.Tietze()]
words[-1] = all_edges[G([-r.Tietze()[-1]])]
while len(words) > 3:
auxedge = AbstractSimplex(1)
face_maps[auxedge] = [O, O]
auxtring = AbstractSimplex(2)
face_maps[auxtring] = [words[1], auxedge, words[0]]
words = [auxedge] + words[2:]
auxtring = AbstractSimplex(2)
face_maps[auxtring] = [words[1], words[2], words[0]]
return SimplicialSet_finite(face_maps, base_point=O)

0 comments on commit cc3da36

Please sign in to comment.