def reynolds_operator(self, poly, chi=None): r""" Compute the Reynolds operator of this finite group `G`. This is the projection from a polynomial ring to the ring of relative invariants [Stu1993]_. If possible, the invariant is returned defined over the base field of the given polynomial ``poly``, otherwise, it is returned over the compositum of the fields involved in the computation. Only implemented for absolute fields. ALGORITHM: Let `K[x]` be a polynomial ring and `\chi` a linear character for `G`. Let .. MATH: K[x]^G_{\chi} = \{f \in K[x] | \pi f = \chi(\pi) f \forall \pi\in G\} be the ring of invariants of `G` relative to `\chi`. Then the Reynold's operator is a map `R` from `K[x]` into `K[x]^G_{\chi}` defined by .. MATH: f \mapsto \frac{1}{|G|} \sum_{ \pi \in G} \chi(\pi) f. INPUT: - ``poly`` -- a polynomial - ``chi`` -- (default: trivial character) a linear group character of this group OUTPUT: an invariant polynomial relative to `\chi` AUTHORS: Rebecca Lauren Miller and Ben Hutz EXAMPLES:: sage: S3 = MatrixGroup(SymmetricGroup(3)) sage: R.<x,y,z> = QQ[] sage: f = x*y*z^3 sage: S3.reynolds_operator(f) 1/3*x^3*y*z + 1/3*x*y^3*z + 1/3*x*y*z^3 :: sage: G = MatrixGroup(CyclicPermutationGroup(4)) sage: chi = G.character(G.character_table()[3]) sage: K.<v> = CyclotomicField(4) sage: R.<x,y,z,w> = K[] sage: G.reynolds_operator(x, chi) 1/4*x + (1/4*v)*y - 1/4*z + (-1/4*v)*w sage: chi = G.character(G.character_table()[2]) sage: R.<x,y,z,w> = QQ[] sage: G.reynolds_operator(x*y, chi) 1/4*x*y + (-1/4*zeta4)*y*z + (1/4*zeta4)*x*w - 1/4*z*w :: sage: K.<i> = CyclotomicField(4) sage: G = MatrixGroup(CyclicPermutationGroup(3)) sage: chi = G.character(G.character_table()[1]) sage: R.<x,y,z> = K[] sage: G.reynolds_operator(x*y^5, chi) 1/3*x*y^5 + (2/3*izeta3^3 + izeta3^2 + 8/3*izeta3 + 1)*x^5*z + (-2/3*izeta3^3 - izeta3^2 - 8/3*izeta3 - 4/3)*y*z^5 sage: R.<x,y,z> = QQbar[] sage: G.reynolds_operator(x*y^5, chi) 1/3*x*y^5 + (-0.1666666666666667? - 0.2886751345948129?*I)*x^5*z + (-0.1666666666666667? + 0.2886751345948129?*I)*y*z^5 :: sage: K.<i> = CyclotomicField(4) sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) sage: chi = Tetra.character(Tetra.character_table()[4]) sage: L.<v> = QuadraticField(-3) sage: R.<x,y> = L[] sage: Tetra.reynolds_operator(x^4) 0 sage: Tetra.reynolds_operator(x^4, chi) 1/4*x^4 + (1/2*v)*x^2*y^2 + 1/4*y^4 sage: R.<x>=L[] sage: LL.<w> = L.extension(x^2+v) sage: R.<x,y> = LL[] sage: Tetra.reynolds_operator(x^4, chi) Traceback (most recent call last): ... NotImplementedError: only implemented for absolute fields :: sage: G = MatrixGroup(DihedralGroup(4)) sage: chi = G.character(G.character_table()[1]) sage: R.<x,y> = QQ[] sage: f = x^4 sage: G.reynolds_operator(f, chi) Traceback (most recent call last): ... TypeError: number of variables in polynomial must match size of matrices sage: R.<x,y,z,w> = QQ[] sage: f = x^3*y sage: G.reynolds_operator(f, chi) 1/8*x^3*y - 1/8*x*y^3 + 1/8*y^3*z - 1/8*y*z^3 - 1/8*x^3*w + 1/8*z^3*w + 1/8*x*w^3 - 1/8*z*w^3 Characteristic p>0 examples:: sage: G = MatrixGroup([[0,1,1,0]]) sage: R.<w,x> = GF(2)[] sage: G.reynolds_operator(x) Traceback (most recent call last): ... NotImplementedError: not implemented when characteristic divides group order :: sage: i = GF(7)(3) sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) sage: chi = G.character(G.character_table()[4]) sage: R.<w,x> = GF(7)[] sage: f = w^5*x + x^6 sage: G.reynolds_operator(f, chi) Traceback (most recent call last): ... NotImplementedError: nontrivial characters not implemented for characteristic > 0 sage: G.reynolds_operator(f) x^6 :: sage: K = GF(3^2,'t') sage: G = MatrixGroup([matrix(K,2,2, [0,K.gen(),1,0])]) sage: R.<x,y> = GF(3)[] sage: G.reynolds_operator(x^8) -x^8 - y^8 :: sage: K = GF(3^2,'t') sage: G = MatrixGroup([matrix(GF(3),2,2, [0,1,1,0])]) sage: R.<x,y> = K[] sage: f = -K.gen()*x sage: G.reynolds_operator(f) (t)*x + (t)*y """ if poly.parent().ngens() != self.degree(): raise TypeError("number of variables in polynomial must match size of matrices") R = FractionField(poly.base_ring()) C = FractionField(self.base_ring()) if chi is None: #then this is the trivial character if R.characteristic() == 0: #non-modular case if C == QQbar or R == QQbar: L = QQbar elif not C.is_absolute() or not R.is_absolute(): raise NotImplementedError("only implemented for absolute fields") else: #create the compositum if C.absolute_degree() == 1: L = R elif R.absolute_degree() == 1: L = C else: L = C.composite_fields(R)[0] elif not R.characteristic().divides(self.order()): if R.characteristic() != C.characteristic(): raise ValueError("base fields must have same characteristic") else: if R.degree() >= C.degree(): L = R else: L = C else: raise NotImplementedError("not implemented when characteristic divides group order") poly = poly.change_ring(L) poly_gens = vector(poly.parent().gens()) F = L.zero() for g in self: F += poly(*g.matrix()*vector(poly.parent().gens())) F /= self.order() return F #non-trivial character case K = chi.values()[0].parent() if R.characteristic() == 0: #extend base_ring to compositum if C == QQbar or K == QQbar or R == QQbar: L = QQbar elif not C.is_absolute() or not K.is_absolute() or not R.is_absolute(): raise NotImplementedError("only implemented for absolute fields") else: fields = [] for M in [R,K,C]: if M.absolute_degree() != 1: fields.append(M) l = len(fields) if l == 0: # all are QQ L = R elif l == 1: #only one is an extension L = fields[0] elif l == 2: #only two are extensions L = fields[0].composite_fields(fields[1])[0] else: #all three are extensions L1 = fields[0].composite_fields(fields[1])[0] L = L1.composite_fields(fields[2])[0] else: raise NotImplementedError("nontrivial characters not implemented for characteristic > 0") poly = poly.change_ring(L) poly_gens = vector(poly.parent().gens()) F = L.zero() for g in self: F += L(chi(g)) * poly(*g.matrix().change_ring(L)*poly_gens) F /= self.order() try: # attempt to move F to base_ring of polyomial F = F.change_ring(R) except (TypeError, ValueError): pass return F
def molien_series(self, chi=None, return_series=True, prec=20, variable='t'): r""" Compute the Molien series of this finite group with respect to the character ``chi``. It can be returned either as a rational function in one variable or a power series in one variable. The base field must be a finite field, the rationals, or a cyclotomic field. Note that the base field characteristic cannot divide the group order (i.e., the non-modular case). ALGORITHM: For a finite group `G` in characteristic zero we construct the Molien series as .. MATH:: \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\text{det}(I-tg)}, where `I` is the identity matrix and `t` an indeterminate. For characteristic `p` not dividing the order of `G`, let `k` be the base field and `N` the order of `G`. Define `\lambda` as a primitive `N`-th root of unity over `k` and `\omega` as a primitive `N`-th root of unity over `\QQ`. For each `g \in G` define `k_i(g)` to be the positive integer such that `e_i = \lambda^{k_i(g)}` for each eigenvalue `e_i` of `g`. Then the Molien series is computed as .. MATH:: \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\prod_{i=1}^n(1 - t\omega^{k_i(g)})}, where `t` is an indeterminant. [Dec1998]_ INPUT: - ``chi`` -- (default: trivial character) a linear group character of this group - ``return_series`` -- boolean (default: ``True``) if ``True``, then returns the Molien series as a power series, ``False`` as a rational function - ``prec`` -- integer (default: 20); power series default precision - ``variable`` -- string (default: ``'t'``); Variable name for the Molien series OUTPUT: single variable rational function or power series with integer coefficients EXAMPLES:: sage: MatrixGroup(matrix(QQ,2,2,[1,1,0,1])).molien_series() Traceback (most recent call last): ... NotImplementedError: only implemented for finite groups sage: MatrixGroup(matrix(GF(3),2,2,[1,1,0,1])).molien_series() Traceback (most recent call last): ... NotImplementedError: characteristic cannot divide group order Tetrahedral Group:: sage: K.<i> = CyclotomicField(4) sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) sage: Tetra.molien_series(prec=30) 1 + t^8 + 2*t^12 + t^16 + 2*t^20 + 3*t^24 + 2*t^28 + O(t^30) sage: mol = Tetra.molien_series(return_series=False); mol (t^8 - t^4 + 1)/(t^16 - t^12 - t^4 + 1) sage: mol.parent() Fraction Field of Univariate Polynomial Ring in t over Integer Ring sage: chi = Tetra.character(Tetra.character_table()[1]) sage: Tetra.molien_series(chi, prec=30, variable='u') u^6 + u^14 + 2*u^18 + u^22 + 2*u^26 + 3*u^30 + 2*u^34 + O(u^36) sage: chi = Tetra.character(Tetra.character_table()[2]) sage: Tetra.molien_series(chi) t^10 + t^14 + t^18 + 2*t^22 + 2*t^26 + O(t^30) :: sage: S3 = MatrixGroup(SymmetricGroup(3)) sage: mol = S3.molien_series(prec=10); mol 1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + 8*t^7 + 10*t^8 + 12*t^9 + O(t^10) sage: mol.parent() Power Series Ring in t over Integer Ring Octahedral Group:: sage: K.<v> = CyclotomicField(8) sage: a = v-v^3 #sqrt(2) sage: i = v^2 sage: Octa = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [(1+i)/a,0, 0,(1-i)/a]) sage: Octa.molien_series(prec=30) 1 + t^8 + t^12 + t^16 + t^18 + t^20 + 2*t^24 + t^26 + t^28 + O(t^30) Icosahedral Group:: sage: K.<v> = CyclotomicField(10) sage: z5 = v^2 sage: i = z5^5 sage: a = 2*z5^3 + 2*z5^2 + 1 #sqrt(5) sage: Ico = MatrixGroup([[z5^3,0, 0,z5^2], [0,1, -1,0], [(z5^4-z5)/a, (z5^2-z5^3)/a, (z5^2-z5^3)/a, -(z5^4-z5)/a]]) sage: Ico.molien_series(prec=40) 1 + t^12 + t^20 + t^24 + t^30 + t^32 + t^36 + O(t^40) :: sage: G = MatrixGroup(CyclicPermutationGroup(3)) sage: chi = G.character(G.character_table()[1]) sage: G.molien_series(chi, prec=10) t + 2*t^2 + 3*t^3 + 5*t^4 + 7*t^5 + 9*t^6 + 12*t^7 + 15*t^8 + 18*t^9 + 22*t^10 + O(t^11) :: sage: K = GF(5) sage: S = MatrixGroup(SymmetricGroup(4)) sage: G = MatrixGroup([matrix(K,4,4,[K(y) for u in m.list() for y in u])for m in S.gens()]) sage: G.molien_series(return_series=False) 1/(t^10 - t^9 - t^8 + 2*t^5 - t^2 - t + 1) :: sage: i = GF(7)(3) sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) sage: chi = G.character(G.character_table()[4]) sage: G.molien_series(chi) 3*t^5 + 6*t^11 + 9*t^17 + 12*t^23 + O(t^25) """ if not self.is_finite(): raise NotImplementedError("only implemented for finite groups") if chi is None: chi = self.trivial_character() M = self.matrix_space() R = FractionField(self.base_ring()) N = self.order() if R.characteristic() == 0: P = PolynomialRing(R, variable) t = P.gen() #it is possible the character is over a larger cyclotomic field K = chi.values()[0].parent() if K.degree() != 1: if R.degree() != 1: L = K.composite_fields(R)[0] else: L = K else: L = R mol = P(0) for g in self: mol += L(chi(g)) / (M.identity_matrix()-t*g.matrix()).det().change_ring(L) elif R.characteristic().divides(N): raise NotImplementedError("characteristic cannot divide group order") else: #char p>0 #find primitive Nth roots of unity over base ring and QQ F = cyclotomic_polynomial(N).change_ring(R) w = F.roots(ring=R.algebraic_closure(), multiplicities=False)[0] #don't need to extend further in this case since the order of #the roots of unity in the character divide the order of the group L = CyclotomicField(N, 'v') v = L.gen() #construct Molien series P = PolynomialRing(L, variable) t = P.gen() mol = P(0) for g in self: #construct Phi phi = L(chi(g)) for e in g.matrix().eigenvalues(): #find power such that w**n = e n = 1 while w**n != e and n < N+1: n += 1 #raise v to that power phi *= (1-t*v**n) mol += P(1)/phi #We know the coefficients will be integers mol = mol.numerator().change_ring(ZZ) / mol.denominator().change_ring(ZZ) #divide by group order mol /= N if return_series: PS = PowerSeriesRing(ZZ, variable, default_prec=prec) return PS(mol) return mol
def reynolds_operator(self, poly, chi=None): r""" Compute the Reynolds operator of this finite group `G`. This is the projection from a polynomial ring to the ring of relative invariants [Stu1993]_. If possible, the invariant is returned defined over the base field of the given polynomial ``poly``, otherwise, it is returned over the compositum of the fields involved in the computation. Only implemented for absolute fields. ALGORITHM: Let `K[x]` be a polynomial ring and `\chi` a linear character for `G`. Let .. MATH: K[x]^G_{\chi} = \{f \in K[x] | \pi f = \chi(\pi) f \forall \pi\in G\} be the ring of invariants of `G` relative to `\chi`. Then the Reynold's operator is a map `R` from `K[x]` into `K[x]^G_{\chi}` defined by .. MATH: f \mapsto \frac{1}{|G|} \sum_{ \pi \in G} \chi(\pi) f. INPUT: - ``poly`` -- a polynomial - ``chi`` -- (default: trivial character) a linear group character of this group OUTPUT: an invariant polynomial relative to `\chi` AUTHORS: Rebecca Lauren Miller and Ben Hutz EXAMPLES:: sage: S3 = MatrixGroup(SymmetricGroup(3)) sage: R.<x,y,z> = QQ[] sage: f = x*y*z^3 sage: S3.reynolds_operator(f) 1/3*x^3*y*z + 1/3*x*y^3*z + 1/3*x*y*z^3 :: sage: G = MatrixGroup(CyclicPermutationGroup(4)) sage: chi = G.character(G.character_table()[3]) sage: K.<v> = CyclotomicField(4) sage: R.<x,y,z,w> = K[] sage: G.reynolds_operator(x, chi) 1/4*x + (-1/4*v)*y - 1/4*z + (1/4*v)*w sage: chi = G.character(G.character_table()[2]) sage: R.<x,y,z,w> = QQ[] sage: G.reynolds_operator(x*y, chi) 1/4*x*y + (1/4*zeta4)*y*z + (-1/4*zeta4)*x*w - 1/4*z*w :: sage: K.<i> = CyclotomicField(4) sage: G = MatrixGroup(CyclicPermutationGroup(3)) sage: chi = G.character(G.character_table()[1]) sage: R.<x,y,z> = K[] sage: G.reynolds_operator(x*y^5, chi) 1/3*x*y^5 + (2/3*izeta3^3 + izeta3^2 + 8/3*izeta3 + 1)*x^5*z + (-2/3*izeta3^3 - izeta3^2 - 8/3*izeta3 - 4/3)*y*z^5 sage: R.<x,y,z> = QQbar[] sage: G.reynolds_operator(x*y^5, chi) 1/3*x*y^5 + (-0.1666666666666667? - 0.2886751345948129?*I)*x^5*z + (-0.1666666666666667? + 0.2886751345948129?*I)*y*z^5 :: sage: K.<i> = CyclotomicField(4) sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) sage: chi = Tetra.character(Tetra.character_table()[4]) sage: L.<v> = QuadraticField(-3) sage: R.<x,y> = L[] sage: Tetra.reynolds_operator(x^4) 0 sage: Tetra.reynolds_operator(x^4, chi) 1/4*x^4 + (1/2*v)*x^2*y^2 + 1/4*y^4 sage: R.<x>=L[] sage: LL.<w> = L.extension(x^2+v) sage: R.<x,y> = LL[] sage: Tetra.reynolds_operator(x^4, chi) Traceback (most recent call last): ... NotImplementedError: only implemented for absolute fields :: sage: G = MatrixGroup(DihedralGroup(4)) sage: chi = G.character(G.character_table()[1]) sage: R.<x,y> = QQ[] sage: f = x^4 sage: G.reynolds_operator(f, chi) Traceback (most recent call last): ... TypeError: number of variables in polynomial must match size of matrices sage: R.<x,y,z,w> = QQ[] sage: f = x^3*y sage: G.reynolds_operator(f, chi) 1/8*x^3*y - 1/8*x*y^3 + 1/8*y^3*z - 1/8*y*z^3 - 1/8*x^3*w + 1/8*z^3*w + 1/8*x*w^3 - 1/8*z*w^3 Characteristic p>0 examples:: sage: G = MatrixGroup([[0,1,1,0]]) sage: R.<w,x> = GF(2)[] sage: G.reynolds_operator(x) Traceback (most recent call last): ... NotImplementedError: not implemented when characteristic divides group order :: sage: i = GF(7)(3) sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) sage: chi = G.character(G.character_table()[4]) sage: R.<w,x> = GF(7)[] sage: f = w^5*x + x^6 sage: G.reynolds_operator(f, chi) Traceback (most recent call last): ... NotImplementedError: nontrivial characters not implemented for characteristic > 0 sage: G.reynolds_operator(f) x^6 :: sage: K = GF(3^2,'t') sage: G = MatrixGroup([matrix(K,2,2, [0,K.gen(),1,0])]) sage: R.<x,y> = GF(3)[] sage: G.reynolds_operator(x^8) -x^8 - y^8 :: sage: K = GF(3^2,'t') sage: G = MatrixGroup([matrix(GF(3),2,2, [0,1,1,0])]) sage: R.<x,y> = K[] sage: f = -K.gen()*x sage: G.reynolds_operator(f) (t)*x + (t)*y """ if poly.parent().ngens() != self.degree(): raise TypeError( "number of variables in polynomial must match size of matrices" ) R = FractionField(poly.base_ring()) C = FractionField(self.base_ring()) if chi is None: #then this is the trivial character if R.characteristic() == 0: #non-modular case if C == QQbar or R == QQbar: L = QQbar elif not C.is_absolute() or not R.is_absolute(): raise NotImplementedError( "only implemented for absolute fields") else: #create the compositum if C.absolute_degree() == 1: L = R elif R.absolute_degree() == 1: L = C else: L = C.composite_fields(R)[0] elif not R.characteristic().divides(self.order()): if R.characteristic() != C.characteristic(): raise ValueError( "base fields must have same characteristic") else: if R.degree() >= C.degree(): L = R else: L = C else: raise NotImplementedError( "not implemented when characteristic divides group order") poly = poly.change_ring(L) poly_gens = vector(poly.parent().gens()) F = L.zero() for g in self: F += poly(*g.matrix() * vector(poly.parent().gens())) F /= self.order() return F #non-trivial character case K = chi.values()[0].parent() if R.characteristic() == 0: #extend base_ring to compositum if C == QQbar or K == QQbar or R == QQbar: L = QQbar elif not C.is_absolute() or not K.is_absolute( ) or not R.is_absolute(): raise NotImplementedError( "only implemented for absolute fields") else: fields = [] for M in [R, K, C]: if M.absolute_degree() != 1: fields.append(M) l = len(fields) if l == 0: # all are QQ L = R elif l == 1: #only one is an extension L = fields[0] elif l == 2: #only two are extensions L = fields[0].composite_fields(fields[1])[0] else: #all three are extensions L1 = fields[0].composite_fields(fields[1])[0] L = L1.composite_fields(fields[2])[0] else: raise NotImplementedError( "nontrivial characters not implemented for characteristic > 0") poly = poly.change_ring(L) poly_gens = vector(poly.parent().gens()) F = L.zero() for g in self: F += L(chi(g)) * poly(*g.matrix().change_ring(L) * poly_gens) F /= self.order() try: # attempt to move F to base_ring of polynomial F = F.change_ring(R) except (TypeError, ValueError): pass return F
def molien_series(self, chi=None, return_series=True, prec=20, variable='t'): r""" Compute the Molien series of this finite group with respect to the character ``chi``. It can be returned either as a rational function in one variable or a power series in one variable. The base field must be a finite field, the rationals, or a cyclotomic field. Note that the base field characteristic cannot divide the group order (i.e., the non-modular case). ALGORITHM: For a finite group `G` in characteristic zero we construct the Molien series as .. MATH:: \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\text{det}(I-tg)}, where `I` is the identity matrix and `t` an indeterminate. For characteristic `p` not dividing the order of `G`, let `k` be the base field and `N` the order of `G`. Define `\lambda` as a primitive `N`-th root of unity over `k` and `\omega` as a primitive `N`-th root of unity over `\QQ`. For each `g \in G` define `k_i(g)` to be the positive integer such that `e_i = \lambda^{k_i(g)}` for each eigenvalue `e_i` of `g`. Then the Molien series is computed as .. MATH:: \frac{1}{|G|}\sum_{g \in G} \frac{\chi(g)}{\prod_{i=1}^n(1 - t\omega^{k_i(g)})}, where `t` is an indeterminant. [Dec1998]_ INPUT: - ``chi`` -- (default: trivial character) a linear group character of this group - ``return_series`` -- boolean (default: ``True``) if ``True``, then returns the Molien series as a power series, ``False`` as a rational function - ``prec`` -- integer (default: 20); power series default precision - ``variable`` -- string (default: ``'t'``); Variable name for the Molien series OUTPUT: single variable rational function or power series with integer coefficients EXAMPLES:: sage: MatrixGroup(matrix(QQ,2,2,[1,1,0,1])).molien_series() Traceback (most recent call last): ... NotImplementedError: only implemented for finite groups sage: MatrixGroup(matrix(GF(3),2,2,[1,1,0,1])).molien_series() Traceback (most recent call last): ... NotImplementedError: characteristic cannot divide group order Tetrahedral Group:: sage: K.<i> = CyclotomicField(4) sage: Tetra = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [0,i, -i,0]) sage: Tetra.molien_series(prec=30) 1 + t^8 + 2*t^12 + t^16 + 2*t^20 + 3*t^24 + 2*t^28 + O(t^30) sage: mol = Tetra.molien_series(return_series=False); mol (t^8 - t^4 + 1)/(t^16 - t^12 - t^4 + 1) sage: mol.parent() Fraction Field of Univariate Polynomial Ring in t over Integer Ring sage: chi = Tetra.character(Tetra.character_table()[1]) sage: Tetra.molien_series(chi, prec=30, variable='u') u^6 + u^14 + 2*u^18 + u^22 + 2*u^26 + 3*u^30 + 2*u^34 + O(u^36) sage: chi = Tetra.character(Tetra.character_table()[2]) sage: Tetra.molien_series(chi) t^10 + t^14 + t^18 + 2*t^22 + 2*t^26 + O(t^30) :: sage: S3 = MatrixGroup(SymmetricGroup(3)) sage: mol = S3.molien_series(prec=10); mol 1 + t + 2*t^2 + 3*t^3 + 4*t^4 + 5*t^5 + 7*t^6 + 8*t^7 + 10*t^8 + 12*t^9 + O(t^10) sage: mol.parent() Power Series Ring in t over Integer Ring Octahedral Group:: sage: K.<v> = CyclotomicField(8) sage: a = v-v^3 #sqrt(2) sage: i = v^2 sage: Octa = MatrixGroup([(-1+i)/2,(-1+i)/2, (1+i)/2,(-1-i)/2], [(1+i)/a,0, 0,(1-i)/a]) sage: Octa.molien_series(prec=30) 1 + t^8 + t^12 + t^16 + t^18 + t^20 + 2*t^24 + t^26 + t^28 + O(t^30) Icosahedral Group:: sage: K.<v> = CyclotomicField(10) sage: z5 = v^2 sage: i = z5^5 sage: a = 2*z5^3 + 2*z5^2 + 1 #sqrt(5) sage: Ico = MatrixGroup([[z5^3,0, 0,z5^2], [0,1, -1,0], [(z5^4-z5)/a, (z5^2-z5^3)/a, (z5^2-z5^3)/a, -(z5^4-z5)/a]]) sage: Ico.molien_series(prec=40) 1 + t^12 + t^20 + t^24 + t^30 + t^32 + t^36 + O(t^40) :: sage: G = MatrixGroup(CyclicPermutationGroup(3)) sage: chi = G.character(G.character_table()[1]) sage: G.molien_series(chi, prec=10) t + 2*t^2 + 3*t^3 + 5*t^4 + 7*t^5 + 9*t^6 + 12*t^7 + 15*t^8 + 18*t^9 + 22*t^10 + O(t^11) :: sage: K = GF(5) sage: S = MatrixGroup(SymmetricGroup(4)) sage: G = MatrixGroup([matrix(K,4,4,[K(y) for u in m.list() for y in u])for m in S.gens()]) sage: G.molien_series(return_series=False) 1/(t^10 - t^9 - t^8 + 2*t^5 - t^2 - t + 1) :: sage: i = GF(7)(3) sage: G = MatrixGroup([[i^3,0,0,-i^3],[i^2,0,0,-i^2]]) sage: chi = G.character(G.character_table()[4]) sage: G.molien_series(chi) 3*t^5 + 6*t^11 + 9*t^17 + 12*t^23 + O(t^25) """ if not self.is_finite(): raise NotImplementedError("only implemented for finite groups") if chi is None: chi = self.trivial_character() M = self.matrix_space() R = FractionField(self.base_ring()) N = self.order() if R.characteristic() == 0: P = PolynomialRing(R, variable) t = P.gen() #it is possible the character is over a larger cyclotomic field K = chi.values()[0].parent() if K.degree() != 1: if R.degree() != 1: L = K.composite_fields(R)[0] else: L = K else: L = R mol = P(0) for g in self: mol += L(chi(g)) / (M.identity_matrix() - t * g.matrix()).det().change_ring(L) elif R.characteristic().divides(N): raise NotImplementedError( "characteristic cannot divide group order") else: #char p>0 #find primitive Nth roots of unity over base ring and QQ F = cyclotomic_polynomial(N).change_ring(R) w = F.roots(ring=R.algebraic_closure(), multiplicities=False)[0] #don't need to extend further in this case since the order of #the roots of unity in the character divide the order of the group L = CyclotomicField(N, 'v') v = L.gen() #construct Molien series P = PolynomialRing(L, variable) t = P.gen() mol = P(0) for g in self: #construct Phi phi = L(chi(g)) for e in g.matrix().eigenvalues(): #find power such that w**n = e n = 1 while w**n != e and n < N + 1: n += 1 #raise v to that power phi *= (1 - t * v**n) mol += P(1) / phi #We know the coefficients will be integers mol = mol.numerator().change_ring(ZZ) / mol.denominator().change_ring( ZZ) #divide by group order mol /= N if return_series: PS = PowerSeriesRing(ZZ, variable, default_prec=prec) return PS(mol) return mol
def binary_quintic_coefficients_from_invariants(invariants, K=None, invariant_choice='default', scaling='none'): r""" Reconstruct a binary quintic from the values of its (Clebsch) invariants. INPUT: - ``invariants`` -- A list or tuple of values of the three or four invariants. The default option requires the Clebsch invariants `A`, `B`, `C` and `R` of the binary quintic. - ``K`` -- The field over which the quintic is defined. - ``invariant_choice`` -- The type of invariants provided. The accepted options are ``'clebsch'`` and ``'default'``, which are the same. No other options are implemented. - ``scaling`` -- How the coefficients should be scaled. The accepted values are ``'none'`` for no scaling, ``'normalized'`` to scale in such a way that the resulting coefficients are independent of the scaling of the input invariants and ``'coprime'`` which scales the input invariants by dividing them by their gcd. OUTPUT: A set of coefficients of a binary quintic, whose invariants are equal to the given ``invariants`` up to a scaling. EXAMPLES: First we check the general case, where the invariant `M` is non-zero:: sage: R.<x0, x1> = QQ[] sage: p = 3*x1^5 + 6*x1^4*x0 + 3*x1^3*x0^2 + 4*x1^2*x0^3 - 5*x1*x0^4 + 4*x0^5 sage: quintic = invariant_theory.binary_quintic(p, x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: reconstructed = invariant_theory.binary_form_from_invariants(5, invs, variables=quintic.variables()) # indirect doctest sage: reconstructed Binary quintic with coefficients (9592267437341790539005557/244140625000000, 2149296928207625556323004064707/610351562500000000, 11149651890347700974453304786783/76293945312500000, 122650775751894638395648891202734239/47683715820312500000, 323996630945706528474286334593218447/11920928955078125000, 1504506503644608395841632538558481466127/14901161193847656250000) We can see that the invariants of the reconstructed form match the ones of the original form by scaling the invariants `B` and `C`:: sage: scale = invs[0]/reconstructed.A_invariant() sage: invs[1] == reconstructed.B_invariant()*scale^2 True sage: invs[2] == reconstructed.C_invariant()*scale^3 True If we compare the form obtained by this reconstruction to the one found by letting the covariants `\alpha` and `\beta` be the coordinates of the form, we find the forms are the same up to a power of the determinant of `\alpha` and `\beta`:: sage: alpha = quintic.alpha_covariant() sage: beta = quintic.beta_covariant() sage: g = matrix([[alpha(x0=1,x1=0),alpha(x0=0,x1=1)],[beta(x0=1,x1=0),beta(x0=0,x1=1)]])^-1 sage: transformed = tuple([g.determinant()^-5*x for x in quintic.transformed(g).coeffs()]) sage: transformed == reconstructed.coeffs() True This can also be seen by computing the `\alpha` covariant of the obtained form:: sage: reconstructed.alpha_covariant().coefficient(x1) 0 sage: reconstructed.alpha_covariant().coefficient(x0) != 0 True If the invariant `M` vanishes, then the coefficients are computed in a different way:: sage: [A,B,C] = [3,1,2] sage: M = 2*A*B - 3*C sage: M 0 sage: from sage.rings.invariants.reconstruction import binary_quintic_coefficients_from_invariants sage: reconstructed = binary_quintic_coefficients_from_invariants([A,B,C]) sage: reconstructed (-66741943359375/2097152, -125141143798828125/134217728, 0, 52793920040130615234375/34359738368, 19797720015048980712890625/1099511627776, -4454487003386020660400390625/17592186044416) sage: newform = sum([ reconstructed[i]*x0^i*x1^(5-i) for i in range(6) ]) sage: newquintic = invariant_theory.binary_quintic(newform, x0, x1) sage: scale = 3/newquintic.A_invariant() sage: [3, newquintic.B_invariant()*scale^2, newquintic.C_invariant()*scale^3] [3, 1, 2] Several special cases:: sage: quintic = invariant_theory.binary_quintic(x0^5 - x1^5, x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 0, 0, 0, 1) sage: quintic = invariant_theory.binary_quintic(x0*x1*(x0^3-x1^3), x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (0, 1, 0, 0, 1, 0) sage: quintic = invariant_theory.binary_quintic(x0^5 + 10*x0^3*x1^2 - 15*x0*x1^4, x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 10, 0, -15, 0) sage: quintic = invariant_theory.binary_quintic(x0^2*(x0^3 + x1^3), x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 0, 1, 0, 0) sage: quintic = invariant_theory.binary_quintic(x0*(x0^4 + x1^4), x0, x1) sage: invs = quintic.clebsch_invariants(as_tuple=True) sage: binary_quintic_coefficients_from_invariants(invs) (1, 0, 0, 0, 1, 0) For fields of characteristic 2, 3 or 5, there is no reconstruction implemented. This is part of :trac:`26786`.:: sage: binary_quintic_coefficients_from_invariants([3,1,2], K=GF(5)) Traceback (most recent call last): ... NotImplementedError: no reconstruction of binary quintics implemented for fields of characteristic 2, 3 or 5 TESTS:: sage: from sage.rings.invariants.reconstruction import binary_quintic_coefficients_from_invariants sage: binary_quintic_coefficients_from_invariants([1,2,3], scaling='unknown') Traceback (most recent call last): ... ValueError: unknown scaling option 'unknown' """ if invariant_choice not in ['default', 'clebsch']: raise ValueError('unknown choice of invariants {} for a binary quintic' .format(invariant_choice)) if scaling not in ['none', 'normalized', 'coprime']: raise ValueError("unknown scaling option '%s'" % scaling) if scaling == 'coprime': if len(invariants) == 3: invariants = _reduce_invariants(invariants, [1,2,3]) elif len(invariants) == 4: invariants = _reduce_invariants(invariants, [2,4,6,9]) A, B, C = invariants[0:3] if K is None: from sage.rings.fraction_field import FractionField K = FractionField(A.parent()) if K.characteristic() in [2, 3, 5]: raise NotImplementedError('no reconstruction of binary quintics ' 'implemented for fields of characteristic 2, 3 or 5') M = 2*A*B - 3*C N = K(2)**-1 * (A*C-B**2) R2 = -K(2)**-1 * (A*N**2-2*B*M*N+C*M**2) scale = [1,1,1,1,1,1] from sage.functions.all import binomial, sqrt if len(invariants) == 3: if R2.is_square(): R = sqrt(R2) else: # if R2 is not a square, we scale the invariants in a suitable way # so that the 'new' R2 is a square [A, B, C] = [R2*A, R2**2*B, R2**3*C] [M, N] = [R2**3*M, R2**4*N] R = R2**5 elif len(invariants) == 4: if invariants[3]**2 != R2: raise ValueError('provided invariants do not satisfy the syzygy ' 'for Clebsch invariants of a binary quintic') R = invariants[3] else: raise ValueError('incorrect number of invariants provided, this ' 'method requires 3 or 4 invariants') if M == 0: if N == 0: if A == 0: raise ValueError('no unique reconstruction possible for ' 'quintics with a treefold linear factor') else: if B == 0: return (1,0,0,0,0,1) else: return (0,1,0,0,1,0) else: # case corresponding to using alpha and gamma as coordinates if A == 0: return (1,0,0,0,1,0) else: if scaling == 'normalized': # scaling z by (R/A**3) scale = [ (-N)**-5*A**6*(R/A**3)**i for i in range(6) ] D = -N Delta = C a = [0] a.append((2*K(3)**-1*A**2-B)*N*B*K(2)**-1 - N**2*K(2)**-1) B0 = 2*K(3)**-1*A*R B1 = A*N*B*K(3)**-1 C0 = 2*K(3)**-1*R C1 = B*N else: # case corresponding to using alpha and beta as coordinates if R == 0: if A == 0: return (1,0,10,0,-15,0) elif scaling == 'normalized': # scaling x by A and z by sqrt(A) scale = [ (-M)**(-5)*sqrt(A)**(12+i) for i in range(6) ] else: if A == 0: if B == 0: return (1,0,0,1,0,0) elif scaling == 'normalized': # scaling y by R/B**2 scale = [ (-M)**(-3)*(R/B**2)**i for i in range(6) ] elif scaling == 'normalized': # scaling y by R/A**4 scale = [ (-M)**(-3)*(R/A**4)**i for i in range(6) ] D = -M Delta = A a = [0] a.append((2*K(3)**-1*A**2-B)*(N*A-M*B)*K(2)**-1 \ - M*(N*K(2)**-1-M*A*K(3)**-1)) B0 = R B1 = K(2)**-1*(N*A-M*B) C0 = 0 C1 = -M a[0] = (2*K(3)**-1*A**2-B)*R a.append(-D*B0 - K(2)**-1*Delta*a[0]) a.append(-D*B1 - K(2)**-1*Delta*a[1]) a.append(D**2*C0 + D*Delta*B0 + K(4)**-1*Delta**2*a[0]) a.append(D**2*C1 + D*Delta*B1 + K(4)**-1*Delta**2*a[1]) coeffs = tuple([K((-1)**i*binomial(5,i)*scale[5-i]*a[i]) for i in range(6)]) if scaling == 'coprime': from sage.arith.misc import gcd return tuple([coeffs[i]/gcd(coeffs) for i in range(6)]) else: return coeffs