Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplicial set group #35097

Merged
merged 8 commits into from
Mar 19, 2023
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method needs doctests.


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)