def fundamental_group(self, simplify=True): r""" Return the fundamental group of this pointed simplicial set. INPUT: - ``simplify`` (bool, optional ``True``) -- if ``False``, then return a presentation of the group in terms of generators and relations. If ``True``, the default, simplify as much as GAP is able to. Algorithm: we compute the edge-path group -- see Section 19 of [Kan1958]_ and :wikipedia:`Fundamental_group`. Choose a spanning tree for the connected component of the 1-skeleton containing the base point, and then the group's generators are given by the non-degenerate edges. There are two types of relations: `e=1` if `e` is in the spanning tree, and for every 2-simplex, if its faces are `e_0`, `e_1`, and `e_2`, then we impose the relation `e_0 e_1^{-1} e_2 = 1`, where we first set `e_i=1` if `e_i` is degenerate. EXAMPLES:: sage: S1 = simplicial_sets.Sphere(1) sage: eight = S1.wedge(S1) sage: eight.fundamental_group() # free group on 2 generators Finitely presented group < e0, e1 | > The fundamental group of a disjoint union of course depends on the choice of base point:: sage: T = simplicial_sets.Torus() sage: K = simplicial_sets.KleinBottle() sage: X = T.disjoint_union(K) sage: X_0 = X.set_base_point(X.n_cells(0)[0]) sage: X_0.fundamental_group().is_abelian() True sage: X_1 = X.set_base_point(X.n_cells(0)[1]) sage: X_1.fundamental_group().is_abelian() False sage: RP3 = simplicial_sets.RealProjectiveSpace(3) sage: RP3.fundamental_group() Finitely presented group < e | e^2 > Compute the fundamental group of some classifying spaces:: sage: C5 = groups.misc.MultiplicativeAbelian([5]) sage: BC5 = C5.nerve() sage: BC5.fundamental_group() Finitely presented group < e0 | e0^5 > 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 > sage: pi.order() 6 sage: pi.is_abelian() False The sphere has a trivial fundamental group:: sage: S2 = simplicial_sets.Sphere(2) sage: S2.fundamental_group() Finitely presented group < | > """ # Import this here to prevent importing libgap upon startup. 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()] 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 = [] for f in skel.n_cells(2): z = dict() for i, sigma in enumerate(skel.faces(f)): if sigma in spanning_tree: z[i] = FG.one() elif sigma.is_degenerate(): z[i] = FG.one() elif sigma in edges: z[i] = FG.gen(gens_dict[sigma]) else: # 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)
def fundamental_group(self, simplify=True): r""" Return the fundamental group of this pointed simplicial set. INPUT: - ``simplify`` (bool, optional ``True``) -- if ``False``, then return a presentation of the group in terms of generators and relations. If ``True``, the default, simplify as much as GAP is able to. Algorithm: we compute the edge-path group -- see Section 19 of [Kan1958]_ and :wikipedia:`Fundamental_group`. Choose a spanning tree for the connected component of the 1-skeleton containing the base point, and then the group's generators are given by the non-degenerate edges. There are two types of relations: `e=1` if `e` is in the spanning tree, and for every 2-simplex, if its faces are `e_0`, `e_1`, and `e_2`, then we impose the relation `e_0 e_1^{-1} e_2 = 1`, where we first set `e_i=1` if `e_i` is degenerate. EXAMPLES:: sage: S1 = simplicial_sets.Sphere(1) sage: eight = S1.wedge(S1) sage: eight.fundamental_group() # free group on 2 generators Finitely presented group < e0, e1 | > The fundamental group of a disjoint union of course depends on the choice of base point:: sage: T = simplicial_sets.Torus() sage: K = simplicial_sets.KleinBottle() sage: X = T.disjoint_union(K) sage: X_0 = X.set_base_point(X.n_cells(0)[0]) sage: X_0.fundamental_group().is_abelian() True sage: X_1 = X.set_base_point(X.n_cells(0)[1]) sage: X_1.fundamental_group().is_abelian() False sage: RP3 = simplicial_sets.RealProjectiveSpace(3) sage: RP3.fundamental_group() Finitely presented group < e | e^2 > Compute the fundamental group of some classifying spaces:: sage: C5 = groups.misc.MultiplicativeAbelian([5]) sage: BC5 = C5.nerve() sage: BC5.fundamental_group() Finitely presented group < e0 | e0^5 > 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 > sage: pi.order() 6 sage: pi.is_abelian() False """ # Import this here to prevent importing libgap upon startup. 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()] 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 gap.TrivialGroup() gens_dict = dict(zip(gens, range(len(gens)))) FG = FreeGroup(len(gens), 'e') rels = [] for f in skel.n_cells(2): z = dict() for i, sigma in enumerate(skel.faces(f)): if sigma in spanning_tree: z[i] = FG.one() elif sigma.is_degenerate(): z[i] = FG.one() elif sigma in edges: z[i] = FG.gen(gens_dict[sigma]) else: # 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)