def fundamental_group(f, simplified=True, projective=False): r""" Return a presentation of the fundamental group of the complement of the algebraic set defined by the polynomial ``f``. INPUT: - ``f`` -- a polynomial in two variables, with coefficients in either the rationals or a number field with a fixed embedding in `\QQbar` - ``simplified`` -- boolean (default: ``True``); if set to ``True`` the presentation will be simplified (see below) - ``projective`` -- boolean (default: ``False``); if set to ``True``, the fundamental group of the complement of the projective completion of the curve will be computed, otherwise, the fundamental group of the complement in the affine plane will be computed If ``simplified`` is ``False``, the returned presentation has as many generators as degree of the polynomial times the points in the base used to create the segments that surround the discriminant. In this case, the generators are granted to be meridians of the curve. OUTPUT: A presentation of the fundamental group of the complement of the curve defined by ``f``. EXAMPLES:: sage: from sage.schemes.curves.zariski_vankampen import fundamental_group # optional - sirocco sage: R.<x,y> = QQ[] sage: f = x^2 + y^3 sage: fundamental_group(f) # optional - sirocco Finitely presented group < ... > sage: fundamental_group(f, simplified=False) # optional - sirocco Finitely presented group < ... > :: sage: from sage.schemes.curves.zariski_vankampen import fundamental_group # optional - sirocco sage: R.<x,y> = QQ[] sage: f = y^3 + x^3 sage: fundamental_group(f) # optional - sirocco Finitely presented group < ... > It is also possible to have coefficients in a number field with a fixed embedding in `\QQbar`:: sage: from sage.schemes.curves.zariski_vankampen import fundamental_group # optional - sirocco sage: zeta = QQbar['x']('x^2+x+1').roots(multiplicities=False)[0] sage: zeta -0.50000000000000000? - 0.866025403784439?*I sage: F = NumberField(zeta.minpoly(), 'zeta', embedding=zeta) sage: F.inject_variables() Defining zeta sage: R.<x,y> = F[] sage: f = y^3 + x^3 +zeta *x + 1 sage: fundamental_group(f) # optional - sirocco Finitely presented group < x0 | > """ (x, y) = f.variables() F = f.base_ring() g = f.factor().radical().prod() d = g.degree(y) while not g.coefficient(y**d) in F or (projective and g.total_degree() > d): g = g.subs({x: x + y}) d = g.degree(y) disc = discrim(g) segs = segments(disc) vertices = list(set(flatten(segs))) Faux = FreeGroup(d) F = FreeGroup(d * len(vertices)) rels = [] if projective: rels.append(prod(F.gen(i) for i in range(d))) braidscomputed = braid_in_segment([(g, seg[0], seg[1]) for seg in segs]) for braidcomputed in braidscomputed: seg = (braidcomputed[0][0][1], braidcomputed[0][0][2]) b = braidcomputed[1] i = vertices.index(seg[0]) j = vertices.index(seg[1]) for k in range(d): el1 = Faux([k + 1]) * b.inverse() el2 = k + 1 w1 = F([sign(a)*d*i + a for a in el1.Tietze()]) w2 = F([d*j + el2]) rels.append(w1/w2) G = F / rels if simplified: return G.simplified() else: return G
def FinitelyGeneratedAbelianPresentation(int_list): r""" Return canonical presentation of finitely generated abelian group. INPUT: - ``int_list`` -- List of integers defining the group to be returned, the defining list is reduced to the invariants of the input list before generating the corresponding group. OUTPUT: Finitely generated abelian group, `\ZZ_{n_1} \times \ZZ_{n_2} \times \cdots \times \ZZ_{n_k}` as a finite presentation, where `n_i` forms the invariants of the input list. EXAMPLES:: sage: groups.presentation.FGAbelian([2,2]) Finitely presented group < a, b | a^2, b^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([2,3]) Finitely presented group < a | a^6 > sage: groups.presentation.FGAbelian([2,4]) Finitely presented group < a, b | a^2, b^4, a^-1*b^-1*a*b > You can create free abelian groups:: sage: groups.presentation.FGAbelian([0]) Finitely presented group < a | > sage: groups.presentation.FGAbelian([0,0]) Finitely presented group < a, b | a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,0,0]) Finitely presented group < a, b, c | a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > And various infinite abelian groups:: sage: groups.presentation.FGAbelian([0,2]) Finitely presented group < a, b | a^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,2,2]) Finitely presented group < a, b, c | a^2, b^2, a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > Outputs are reduced to minimal generators and relations:: sage: groups.presentation.FGAbelian([3,5,2,7,3]) Finitely presented group < a, b | a^3, b^210, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([3,210]) Finitely presented group < a, b | a^3, b^210, a^-1*b^-1*a*b > The trivial group is an acceptable output:: sage: groups.presentation.FGAbelian([]) Finitely presented group < | > sage: groups.presentation.FGAbelian([1]) Finitely presented group < | > sage: groups.presentation.FGAbelian([1,1,1,1,1,1,1,1,1,1]) Finitely presented group < | > Input list must consist of positive integers:: sage: groups.presentation.FGAbelian([2,6,3,9,-4]) Traceback (most recent call last): ... ValueError: input list must contain nonnegative entries sage: groups.presentation.FGAbelian([2,'a',4]) Traceback (most recent call last): ... TypeError: unable to convert 'a' to an integer TESTS:: sage: ag = groups.presentation.FGAbelian([2,2]) sage: ag.as_permutation_group().is_isomorphic(groups.permutation.KleinFour()) True sage: G = groups.presentation.FGAbelian([2,4,8]) sage: C2 = CyclicPermutationGroup(2) sage: C4 = CyclicPermutationGroup(4) sage: C8 = CyclicPermutationGroup(8) sage: gg = (C2.direct_product(C4)[0]).direct_product(C8)[0] sage: gg.is_isomorphic(G.as_permutation_group()) True sage: all(groups.presentation.FGAbelian([i]).as_permutation_group().is_isomorphic(groups.presentation.Cyclic(i).as_permutation_group()) for i in [2..35]) True """ from sage.groups.free_group import _lexi_gen check_ls = [Integer(x) for x in int_list if Integer(x) >= 0] if len(check_ls) != len(int_list): raise ValueError('input list must contain nonnegative entries') col_sp = diagonal_matrix(int_list).column_space() invariants = FGP_Module(ZZ**(len(int_list)), col_sp).invariants() name_gen = _lexi_gen() F = FreeGroup([next(name_gen) for i in invariants]) ret_rls = [ F([i + 1])**invariants[i] for i in range(len(invariants)) if invariants[i] != 0 ] # Build commutator relations gen_pairs = [[F.gen(i), F.gen(j)] for i in range(F.ngens() - 1) for j in range(i + 1, F.ngens())] ret_rls = ret_rls + [ x[0]**(-1) * x[1]**(-1) * x[0] * x[1] for x in gen_pairs ] return FinitelyPresentedGroup(F, tuple(ret_rls))
def FinitelyGeneratedHeisenbergPresentation(n=1, p=0): r""" Return a finite presentation of the Heisenberg group. The Heisenberg group is the group of `(n+2) \times (n+2)` matrices over a ring `R` with diagonal elements equal to 1, first row and last column possibly nonzero, and all the other entries equal to zero. INPUT: - ``n`` -- the degree of the Heisenberg group - ``p`` -- (optional) a prime number, where we construct the Heisenberg group over the finite field `\ZZ/p\ZZ` OUTPUT: Finitely generated Heisenberg group over the finite field of order ``p`` or over the integers. .. SEEALSO:: :class:`~sage.groups.matrix_gps.heisenberg.HeisenbergGroup` EXAMPLES:: sage: H = groups.presentation.Heisenberg(); H Finitely presented group < x1, y1, z | x1*y1*x1^-1*y1^-1*z^-1, z*x1*z^-1*x1^-1, z*y1*z^-1*y1^-1 > sage: H.order() +Infinity sage: r1, r2, r3 = H.relations() sage: A = matrix([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) sage: B = matrix([[1, 0, 0], [0, 1, 1], [0, 0, 1]]) sage: C = matrix([[1, 0, 1], [0, 1, 0], [0, 0, 1]]) sage: r1(A, B, C) [1 0 0] [0 1 0] [0 0 1] sage: r2(A, B, C) [1 0 0] [0 1 0] [0 0 1] sage: r3(A, B, C) [1 0 0] [0 1 0] [0 0 1] sage: p = 3 sage: Hp = groups.presentation.Heisenberg(p=3) sage: Hp.order() == p**3 True sage: Hnp = groups.presentation.Heisenberg(n=2, p=3) sage: len(Hnp.relations()) 13 REFERENCES: - :wikipedia:`Heisenberg_group` """ n = Integer(n) if n < 1: raise ValueError('n must be a positive integer') # generators' names are x1, .., xn, y1, .., yn, z vx = ['x' + str(i) for i in range(1, n + 1)] vy = ['y' + str(i) for i in range(1, n + 1)] str_generators = ', '.join(vx + vy + ['z']) F = FreeGroup(str_generators) x = F.gens()[0:n] # list of generators x1, x2, ..., xn y = F.gens()[n:2 * n] # list of generators x1, x2, ..., xn z = F.gen(n * 2) def commutator(a, b): return a * b * a**-1 * b**-1 # First set of relations: [xi, yi] = z r1 = [commutator(x[i], y[i]) * z**-1 for i in range(n)] # Second set of relations: [z, xi] = 1 r2 = [commutator(z, x[i]) for i in range(n)] # Third set of relations: [z, yi] = 1 r3 = [commutator(z, y[i]) for i in range(n)] # Fourth set of relations: [xi, yi] = 1 for i != j r4 = [commutator(x[i], y[j]) for i in range(n) for j in range(n) if i != j] rls = r1 + r2 + r3 + r4 from sage.sets.primes import Primes if p not in Primes() and p != 0: raise ValueError("p must be 0 or a prime number") if p > 0: rls += [w**p for w in F.gens()] return FinitelyPresentedGroup(F, tuple(rls))
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 FinitelyGeneratedHeisenbergPresentation(n=1, p=0): r""" Return a finite presentation of the Heisenberg group. The Heisenberg group is the group of `(n+2) \times (n+2)` matrices over a ring `R` with diagonal elements equal to 1, first row and last column possibly nonzero, and all the other entries equal to zero. INPUT: - ``n`` -- the degree of the Heisenberg group - ``p`` -- (optional) a prime number, where we construct the Heisenberg group over the finite field `\ZZ/p\ZZ` OUTPUT: Finitely generated Heisenberg group over the finite field of order ``p`` or over the integers. .. SEEALSO:: :class:`~sage.groups.matrix_gps.heisenberg.HeisenbergGroup` EXAMPLES:: sage: H = groups.presentation.Heisenberg(); H Finitely presented group < x1, y1, z | x1*y1*x1^-1*y1^-1*z^-1, z*x1*z^-1*x1^-1, z*y1*z^-1*y1^-1 > sage: H.order() +Infinity sage: r1, r2, r3 = H.relations() sage: A = matrix([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) sage: B = matrix([[1, 0, 0], [0, 1, 1], [0, 0, 1]]) sage: C = matrix([[1, 0, 1], [0, 1, 0], [0, 0, 1]]) sage: r1(A, B, C) [1 0 0] [0 1 0] [0 0 1] sage: r2(A, B, C) [1 0 0] [0 1 0] [0 0 1] sage: r3(A, B, C) [1 0 0] [0 1 0] [0 0 1] sage: p = 3 sage: Hp = groups.presentation.Heisenberg(p=3) sage: Hp.order() == p**3 True sage: Hnp = groups.presentation.Heisenberg(n=2, p=3) sage: len(Hnp.relations()) 13 REFERENCES: - :wikipedia:`Heisenberg_group` """ n = Integer(n) if n < 1: raise ValueError('n must be a positive integer') # generators' names are x1, .., xn, y1, .., yn, z vx = ['x' + str(i) for i in range(1,n+1)] vy = ['y' + str(i) for i in range(1,n+1)] str_generators = ', '.join(vx + vy + ['z']) F = FreeGroup(str_generators) x = F.gens()[0:n] # list of generators x1, x2, ..., xn y = F.gens()[n:2*n] # list of generators x1, x2, ..., xn z = F.gen(n*2) def commutator(a, b): return a * b * a**-1 * b**-1 # First set of relations: [xi, yi] = z r1 = [commutator(x[i], y[i]) * z**-1 for i in range(n)] # Second set of relations: [z, xi] = 1 r2 = [commutator(z, x[i]) for i in range(n)] # Third set of relations: [z, yi] = 1 r3 = [commutator(z, y[i]) for i in range(n)] # Fourth set of relations: [xi, yi] = 1 for i != j r4 = [commutator(x[i], y[j]) for i in range(n) for j in range(n) if i!=j] rls = r1 + r2 + r3 + r4 from sage.sets.primes import Primes if p not in Primes() and p != 0: raise ValueError("p must be 0 or a prime number") if p > 0: rls += [w**p for w in F.gens()] return FinitelyPresentedGroup(F, tuple(rls))
def FinitelyGeneratedAbelianPresentation(int_list): r""" Return canonical presentation of finitely generated abelian group. INPUT: - ``int_list`` -- List of integers defining the group to be returned, the defining list is reduced to the invariants of the input list before generating the corresponding group. OUTPUT: Finitely generated abelian group, `\ZZ_{n_1} \times \ZZ_{n_2} \times \cdots \times \ZZ_{n_k}` as a finite presentation, where `n_i` forms the invariants of the input list. EXAMPLES:: sage: groups.presentation.FGAbelian([2,2]) Finitely presented group < a, b | a^2, b^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([2,3]) Finitely presented group < a | a^6 > sage: groups.presentation.FGAbelian([2,4]) Finitely presented group < a, b | a^2, b^4, a^-1*b^-1*a*b > You can create free abelian groups:: sage: groups.presentation.FGAbelian([0]) Finitely presented group < a | > sage: groups.presentation.FGAbelian([0,0]) Finitely presented group < a, b | a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,0,0]) Finitely presented group < a, b, c | a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > And various infinite abelian groups:: sage: groups.presentation.FGAbelian([0,2]) Finitely presented group < a, b | a^2, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([0,2,2]) Finitely presented group < a, b, c | a^2, b^2, a^-1*b^-1*a*b, a^-1*c^-1*a*c, b^-1*c^-1*b*c > Outputs are reduced to minimal generators and relations:: sage: groups.presentation.FGAbelian([3,5,2,7,3]) Finitely presented group < a, b | a^3, b^210, a^-1*b^-1*a*b > sage: groups.presentation.FGAbelian([3,210]) Finitely presented group < a, b | a^3, b^210, a^-1*b^-1*a*b > The trivial group is an acceptable output:: sage: groups.presentation.FGAbelian([]) Finitely presented group < | > sage: groups.presentation.FGAbelian([1]) Finitely presented group < | > sage: groups.presentation.FGAbelian([1,1,1,1,1,1,1,1,1,1]) Finitely presented group < | > Input list must consist of positive integers:: sage: groups.presentation.FGAbelian([2,6,3,9,-4]) Traceback (most recent call last): ... ValueError: input list must contain nonnegative entries sage: groups.presentation.FGAbelian([2,'a',4]) Traceback (most recent call last): ... TypeError: unable to convert 'a' to an integer TESTS:: sage: ag = groups.presentation.FGAbelian([2,2]) sage: ag.as_permutation_group().is_isomorphic(groups.permutation.KleinFour()) True sage: G = groups.presentation.FGAbelian([2,4,8]) sage: C2 = CyclicPermutationGroup(2) sage: C4 = CyclicPermutationGroup(4) sage: C8 = CyclicPermutationGroup(8) sage: gg = (C2.direct_product(C4)[0]).direct_product(C8)[0] sage: gg.is_isomorphic(G.as_permutation_group()) True sage: all(groups.presentation.FGAbelian([i]).as_permutation_group().is_isomorphic(groups.presentation.Cyclic(i).as_permutation_group()) for i in [2..35]) True """ from sage.groups.free_group import _lexi_gen check_ls = [Integer(x) for x in int_list if Integer(x) >= 0] if len(check_ls) != len(int_list): raise ValueError('input list must contain nonnegative entries') col_sp = diagonal_matrix(int_list).column_space() invariants = FGP_Module(ZZ**(len(int_list)), col_sp).invariants() name_gen = _lexi_gen() F = FreeGroup([next(name_gen) for i in invariants]) ret_rls = [F([i+1])**invariants[i] for i in range(len(invariants)) if invariants[i]!=0] # Build commutator relations gen_pairs = [[F.gen(i),F.gen(j)] for i in range(F.ngens()-1) for j in range(i+1,F.ngens())] ret_rls = ret_rls + [x[0]**(-1)*x[1]**(-1)*x[0]*x[1] for x in gen_pairs] return FinitelyPresentedGroup(F, tuple(ret_rls))
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)