def to_matrix(self): """ Return a matrix of ``self``. The colors are mapped to roots of unity. EXAMPLES:: sage: C = ColoredPermutations(4, 3) sage: s1,s2,t = C.gens() sage: x = s1*s2*t*s2; x.one_line_form() [(1, 2), (0, 1), (0, 3)] sage: M = x.to_matrix(); M [ 0 1 0] [zeta4 0 0] [ 0 0 1] The matrix multiplication is in the *opposite* order:: sage: M == s2.to_matrix()*t.to_matrix()*s2.to_matrix()*s1.to_matrix() True """ Cp = CyclotomicField(self.parent()._m) g = Cp.gen() D = diagonal_matrix(Cp, [g**i for i in self._colors]) return self._perm.to_matrix() * D
def _calcMatrixTrans(calc, tS, tT, lS, lT): """ See `calcMatrixTrans()` for the main documentation. This is the lower-level function which is wrapped through `persistent_cache`. This makes the cache more flexible about different parameters `R` to `calcMatrixTrans()`. """ try: ms = calc.calcMatrixTrans(tS, tT, lS, lT) except Exception: print (calc.params, calc.curlS, tS, tT, lS, lT) raise # Each matrix is for a zeta**i factor, where zeta is the n-th root of unity. # And n = calc.matrixCountTrans. assert len(ms) == calc.matrixCountTrans order = len(ms) K = CyclotomicField(order) zeta = K.gen() Kcoords = zeta.coordinates_in_terms_of_powers() assert len(K.power_basis()) == K.degree() new_ms = [matrix(QQ, ms[0].nrows(), ms[0].ncols()) for i in range(K.degree())] for l in range(order): coords = Kcoords(zeta**l) for i,m in enumerate(coords): new_ms[i] += ms[l] * m ms = new_ms denom = calc.matrixRowDenomTrans denom, ms = reduceNRow(denom=denom, mats=ms) return denom, order, ms
def to_matrix(self): """ Return a matrix of ``self``. The colors are mapped to roots of unity. EXAMPLES:: sage: C = ColoredPermutations(4, 3) sage: s1,s2,t = C.gens() sage: x = s1*s2*t*s2; x.one_line_form() [(1, 2), (0, 1), (0, 3)] sage: M = x.to_matrix(); M [ 0 1 0] [zeta4 0 0] [ 0 0 1] The matrix multiplication is in the *opposite* order:: sage: M == s2.to_matrix()*t.to_matrix()*s2.to_matrix()*s1.to_matrix() True """ Cp = CyclotomicField(self.parent()._m) g = Cp.gen() D = diagonal_matrix(Cp, [g ** i for i in self._colors]) return self._perm.to_matrix() * D
def eis_phipsi(phi, psi, k, prec=10, t=1, cmplx=False): r""" Return Fourier expansion of Eisenstein series at the cusp oo. INPUT: - ``phi`` -- Dirichlet character. - ``psi`` -- Dirichlet character. - ``k`` -- integer, the weight of the Eistenstein series. - ``prec`` -- integer (default: 10). - ``t`` -- integer (default: 1). OUTPUT: The Fourier expansion of the Eisenstein series $E_k^{\phi,\psi, t}$ (as defined by [Diamond-Shurman]). EXAMPLES: sage: phi = DirichletGroup(3)[1] sage: psi = DirichletGroup(5)[1] sage: E = eisenstein_series_at_inf(phi, psi, 4) """ N1, N2 = phi.level(), psi.level() N = N1 * N2 #The Fourier expansion of the Eisenstein series at infinity is in the field Q(zeta_Ncyc) Ncyc = lcm([euler_phi(N1), euler_phi(N2)]) if cmplx == True: CC = ComplexField(53) pi = ComplexField().pi() I = ComplexField().gen() R = CC zeta = CC(exp(2 * pi * I / Ncyc)) else: R = CyclotomicField(Ncyc) zeta = R.zeta(Ncyc) phi, psi = phi.base_extend(R), psi.base_extend(R) Q = PowerSeriesRing(R, 'q') q = Q.gen() s = O(q**prec) #Weight 2 with trivial characters is calculated separately if k == 2 and phi.conductor() == 1 and psi.conductor() == 1: if t == 1: raise TypeError('E_2 is not a modular form.') s = 1 / 24 * (t - 1) for m in srange(1, prec): for n in srange(1, prec / m + 1): s += n * (q**(m * n) - t * q**(m * n * t)) return s + O(q**prec) if psi.level() == 1 and k == 1: s -= phi.bernoulli(k) / k elif phi.level() == 1: s -= psi.bernoulli(k) / k for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += 2 * phi(m) * psi(n) * n**(k - 1) * q**(m * n * t) return s + O(q**prec)
def to_cyclotomic_field(self, R=None): r""" Return this element as an element of a cyclotomic field. EXAMPLES:: sage: UCF = UniversalCyclotomicField() sage: UCF.gen(3).to_cyclotomic_field() zeta3 sage: UCF.gen(3,2).to_cyclotomic_field() -zeta3 - 1 sage: CF = CyclotomicField(5) sage: CF(E(5)) # indirect doctest zeta5 sage: CF = CyclotomicField(7) sage: CF(E(5)) # indirect doctest Traceback (most recent call last): ... TypeError: Cannot coerce zeta5 into Cyclotomic Field of order 7 and degree 6 sage: CF = CyclotomicField(10) sage: CF(E(5)) # indirect doctest zeta10^2 Matrices are correctly dealt with:: sage: M = Matrix(UCF,2,[E(3),E(4),E(5),E(6)]); M [ E(3) E(4)] [ E(5) -E(3)^2] sage: Matrix(CyclotomicField(60),M) # indirect doctest [zeta60^10 - 1 zeta60^15] [ zeta60^12 zeta60^10] Using a non-standard embedding:: sage: CF = CyclotomicField(5,embedding=CC(exp(4*pi*i/5))) sage: x = E(5) sage: CC(x) 0.309016994374947 + 0.951056516295154*I sage: CC(CF(x)) 0.309016994374947 + 0.951056516295154*I """ from sage.rings.number_field.number_field import CyclotomicField k = self._obj.Conductor().sage() Rcan = CyclotomicField(k) if R is None: R = Rcan obj = self._obj if obj.IsRat(): return R(obj.sage()) zeta = Rcan.gen() coeffs = obj.CoeffsCyc(k).sage() return R(sum(coeffs[a] * zeta**a for a in range(1, k)))
def to_cyclotomic_field(self, R=None): r""" Return this element as an element of a cyclotomic field. EXAMPLES:: sage: UCF = UniversalCyclotomicField() sage: UCF.gen(3).to_cyclotomic_field() zeta3 sage: UCF.gen(3,2).to_cyclotomic_field() -zeta3 - 1 sage: CF = CyclotomicField(5) sage: CF(E(5)) # indirect doctest zeta5 sage: CF = CyclotomicField(7) sage: CF(E(5)) # indirect doctest Traceback (most recent call last): ... TypeError: Cannot coerce zeta5 into Cyclotomic Field of order 7 and degree 6 sage: CF = CyclotomicField(10) sage: CF(E(5)) # indirect doctest zeta10^2 Matrices are correctly dealt with:: sage: M = Matrix(UCF,2,[E(3),E(4),E(5),E(6)]); M [ E(3) E(4)] [ E(5) -E(3)^2] sage: Matrix(CyclotomicField(60),M) # indirect doctest [zeta60^10 - 1 zeta60^15] [ zeta60^12 zeta60^10] Using a non-standard embedding:: sage: CF = CyclotomicField(5,embedding=CC(exp(4*pi*i/5))) sage: x = E(5) sage: CC(x) 0.309016994374947 + 0.951056516295154*I sage: CC(CF(x)) 0.309016994374947 + 0.951056516295154*I """ from sage.rings.number_field.number_field import CyclotomicField k = self._obj.Conductor().sage() Rcan = CyclotomicField(k) if R is None: R = Rcan obj = self._obj if obj.IsRat(): return R(obj.sage()) zeta = Rcan.gen() coeffs = obj.CoeffsCyc(k).sage() return R(sum(coeffs[a] * zeta**a for a in range(1,k)))
def _rank(self, K) : if K is QQ or K in NumberFields() : return len(_jacobi_forms_by_taylor_expansion_coords(self.__index, self.__weight, 0)) ## This is the formula used by Poor and Yuen in Paramodular cusp forms if self.__weight == 2 : delta = len(self.__index.divisors()) // 2 - 1 else : delta = 0 return sum( ModularForms(1, self.__weight + 2 * j).dimension() + j**2 // (4 * self.__index) for j in xrange(self.__index + 1) ) \ + delta ## This is the formula given by Skoruppa in ## Jacobi forms of critical weight and Weil representations ##FIXME: There is some mistake here if self.__weight % 2 != 0 : ## Otherwise the space X(i**(n - 2 k)) is different ## See: Skoruppa, Jacobi forms of critical weight and Weil representations raise NotImplementedError m = self.__index K = CyclotomicField(24 * m, 'zeta') zeta = K.gen(0) quadform = lambda x : 6 * x**2 bilinform = lambda x,y : quadform(x + y) - quadform(x) - quadform(y) T = diagonal_matrix([zeta**quadform(i) for i in xrange(2*m)]) S = sum(zeta**(-quadform(x)) for x in xrange(2 * m)) / (2 * m) \ * matrix([[zeta**(-bilinform(j,i)) for j in xrange(2*m)] for i in xrange(2*m)]) subspace_matrix_1 = matrix( [ [1 if j == i or j == 2*m - i else 0 for j in xrange(m + 1) ] for i in xrange(2*m)] ) subspace_matrix_2 = zero_matrix(ZZ, m + 1, 2*m) subspace_matrix_2.set_block(0,0,identity_matrix(m+1)) T = subspace_matrix_2 * T * subspace_matrix_1 S = subspace_matrix_2 * S * subspace_matrix_1 sqrt3 = (zeta**(4*m) - zeta**(-4*m)) * zeta**(-6*m) rank = (self.__weight - 1/2 - 1) / 2 * (m + 1) \ + 1/8 * ( zeta**(3*m * (2*self.__weight - 1)) * S.trace() + zeta**(3*m * (1 - 2*self.__weight)) * S.trace().conjugate() ) \ + 2/(3*sqrt3) * ( zeta**(4 * m * self.__weight) * (S*T).trace() + zeta**(-4 * m * self.__weight) * (S*T).trace().conjugate() ) \ - sum((j**2 % (m+1))/(m+1) -1/2 for j in range(0,m+1)) if self.__weight > 5 / 2 : return rank else : raise NotImplementedError raise NotImplementedError
def __init__(self, n): """ Initialize ``self``. EXAMPLES:: sage: G = groups.matrix.BinaryDihedral(4) sage: TestSuite(G).run() """ self._n = n if n % 2 == 0: R = CyclotomicField(2 * n) zeta = R.gen() i = R.gen()**(n // 2) else: R = CyclotomicField(4 * n) zeta = R.gen()**2 i = R.gen()**n MS = MatrixSpace(R, 2) zero = R.zero() gens = [MS([zeta, zero, zero, ~zeta]), MS([zero, i, i, zero])] from sage.libs.gap.libgap import libgap gap_gens = [libgap(matrix_gen) for matrix_gen in gens] gap_group = libgap.Group(gap_gens) FinitelyGeneratedMatrixGroup_gap.__init__(self, ZZ(2), R, gap_group, category=Groups().Finite())
def toLowerCyclBase(ms, old_order, new_order): """ Let's K_old = CyclotomicField(old_order) , K_new = CyclotomicField(new_order) . We transform the matrices in power base from `K_old` to `K_new`. The way this is implemented works only if `old_order` is a multiple of `new_order`. INPUT: - `ms` -- A list of matrices where every matrix is a factor to `zeta_old**i` where `zeta_old = K_old.gen()` and `len(ms) == K_old.degree()`. - `old_order` -- The order of `K_old`. - `new_order` -- The order of `K_new`. OUTPUT: - A list of matrices `new_ms` where every matrix is a factor to `zeta_new**i` where `zeta_new = K_new.gen()` and `len(new_ms) == K_new.degree()`. """ assert isinstance(ms, list) # list of matrices assert old_order % new_order == 0 K_old = CyclotomicField(old_order) old_degree = int(ZZ(K_old.degree())) K_new = CyclotomicField(new_order) new_degree = int(ZZ(K_new.degree())) assert old_degree % new_degree == 0 assert len(ms) == old_degree new_ms = [None] * new_degree for i in range(old_degree): i2,rem = divmod(i, old_degree / new_degree) if rem == 0: new_ms[i2] = ms[i] else: if ms[i] != 0: return None return new_ms
def reduce_numberfield(a,K): r""" INPUT: a - an element that has the method minpoly. One should multiply a, so it becomes integral, otherwise the result might not be correct. K - number field, such that a is in K. If d is a number we set K = Q(zeta_d) OUTPUT: A representation of a in terms of zeta_d EXAMPLE: sage: zeta294 = CyclotomicField(294).gen() sage: a = 2/7*zeta294^77 - 6/7*zeta294^63 - 8/7*zeta294^56 + 5/7*zeta294^42 - 2/7*zeta294^28 - 4/7*zeta294^21 + 8/7*zeta294^7 + 3/7 sage: reduce_cyclotomic(a*7,7) -2*zeta7^5 - 4*zeta7^4 - 6*zeta7^3 - 8*zeta7^2 - 3*zeta7 - 5 """ if K in ZZ: K = CyclotomicField(K) R = PolynomialRing(K, 'X') X = R.gen() candidates = [-p[0][0] for p in R(a.minpoly()).factor()] distances = [abs(CC(x)-CC(a)) for x in candidates] if min(distances)>.001: return 'Are you sure that a is in {}?'.format(K) return candidates[distances.index(min(distances))]
def hurwitz_hat(d, N, l, zetaN=None): r""" Computes the function zeta_hat(d/N, l) from Brunaults paper "Regulateurs modulaires via la methode de Rogers--Zudilin" as an element of Q(zetaN) at a non-positive integer l. We use the formula sum_{x in Z/NZ} zetaN**(xu) zeta(x/N, s) = N**s zeta_hat(u/N, s). INPUT: - d - int - N - int, positive int - l - int, non-positive integer OUTPUT: - an element of the ring Q(zetaN), equal to zeta_hat(d/N, l) """ if zetaN == None: zetaN = CyclotomicField(N).gen() d = d % N k = 1 - l if k < 1: raise ValueError, 'k has to be positive' if k == 1: s = -QQ(1) / QQ( 2) # This is the term in the sum corresponding to x = 0 s += sum([ zetaN**(x * d) * (QQ(1) / QQ(2) - QQ(d) / QQ(N) + floor(QQ(d) / QQ(N))) for x in range(1, N) ]) else: s = sum([ -zetaN**(x * d) * (ber_pol(QQ(x) / QQ(N) - floor(QQ(x) / QQ(N)), k)) / k for x in range(N) ]) return N**(k - 1) * s
def __init__(self, ray_class_group, base_ring): names = tuple([ "chi%i"%i for i in range(ray_class_group.ngens()) ]) if base_ring is None: from sage.rings.number_field.number_field import CyclotomicField base_ring = CyclotomicField(LCM(ray_class_group.gens_orders())) DualAbelianGroup_class.__init__(self, ray_class_group, names, base_ring) """ ray_class_group accessible as self.group() """
def idft(self): """ A discrete inverse Fourier transform. Only works over `\QQ`. EXAMPLES:: sage: J = range(5) sage: A = [ZZ(1) for i in J] sage: s = IndexedSequence(A,J) sage: fs = s.dft(); fs Indexed sequence: [5, 0, 0, 0, 0] indexed by [0, 1, 2, 3, 4] sage: it = fs.idft(); it Indexed sequence: [1, 1, 1, 1, 1] indexed by [0, 1, 2, 3, 4] sage: it == s True """ F = self.base_ring() ## elements must be coercible into QQ(zeta_N) J = self.index_object() ## must be = range(N) N = len(J) S = self.list() zeta = CyclotomicField(N).gen() iFT = [sum([S[i]*zeta**(-i*j) for i in J]) for j in J] if not(J[0] in ZZ) or F.base_ring().fraction_field() != QQ: raise NotImplementedError("Sorry this type of idft is not implemented yet.") return IndexedSequence(iFT,J)*(Integer(1)/N)
def eis_H(a, b, N, k, Q=None, t=1, prec=10): if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q') R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() a = ZZ(a % N) b = ZZ(b % N) s = 0 if k == 1: if a == 0 and not b == 0: s = -QQ(1) / QQ(2) * (1 + zetaN**b) / (1 - zetaN**b) elif b == 0 and not a == 0: s = -QQ(1) / QQ(2) * (1 + zetaN**a) / (1 - zetaN**a) elif a != 0 and b != 0: s = -QQ(1) / QQ(2) * ((1 + zetaN**a) / (1 - zetaN**a) + (1 + zetaN**b) / (1 - zetaN**b)) elif k > 1: s = hurwitz_hat(-b, N, 1 - k, zetaN) for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += (zetaN**(-a * m - b * n) + (-1)**k * zetaN**(a * m + b * n)) * n**(k - 1) * q**(m * n) return s + O(q**floor(prec))
def __init__(self, n): """ Initialize ``self``. EXAMPLES:: sage: G = groups.matrix.BinaryDihedral(4) sage: TestSuite(G).run() """ self._n = n if n % 2 == 0: R = CyclotomicField(2*n) zeta = R.gen() i = R.gen()**(n//2) else: R = CyclotomicField(4*n) zeta = R.gen()**2 i = R.gen()**n MS = MatrixSpace(R, 2) zero = R.zero() gens = [ MS([zeta, zero, zero, ~zeta]), MS([zero, i, i, zero]) ] from sage.libs.gap.libgap import libgap gap_gens = [libgap(matrix_gen) for matrix_gen in gens] gap_group = libgap.Group(gap_gens) FinitelyGeneratedMatrixGroup_gap.__init__(self, ZZ(2), R, gap_group, category=Groups().Finite())
def __init__(self, ray_class_group, base_ring): names = tuple(["chi%i" % i for i in range(ray_class_group.ngens())]) if base_ring is None: from sage.rings.number_field.number_field import CyclotomicField # FIXME: following is deprecated starting from sage 7.1 # replace by from sage.arith.all import LCM from sage.rings.arith import LCM base_ring = CyclotomicField(LCM(ray_class_group.gens_orders())) DualAbelianGroup_class.__init__(self, ray_class_group, names, base_ring) """ ray_class_group accessible as self.group() """
def __init__(self, G, names="X", bse_ring=None): """ If G has invariants invs = [n1,...,nk] then the default base_ring is CyclotoomicField(N), where N = LCM(n1,...,nk). """ if bse_ring == None: base_ring = CyclotomicField(LCM(G.invariants())) else: base_ring = bse_ring self.__group = G self._assign_names(names) self._base_ring = base_ring
def extract(cls, obj): """ Takes an object extracted by the json parser and decodes the special-formating dictionaries used to store special types. """ if isinstance(obj, dict) and 'data' in obj: if len(obj) == 2 and '__ComplexList__' in obj: return [complex(*v) for v in obj['data']] elif len(obj) == 2 and '__QQList__' in obj: return [QQ(tuple(v)) for v in obj['data']] elif len(obj) == 3 and '__NFList__' in obj and 'base' in obj: base = cls.extract(obj['base']) return [cls._extract(base, c) for c in obj['data']] elif len(obj) == 2 and '__IntDict__' in obj: return {Integer(k): cls.extract(v) for k,v in obj['data']} elif len(obj) == 3 and '__Vector__' in obj and 'base' in obj: base = cls.extract(obj['base']) return vector([cls._extract(base, v) for v in obj['data']]) elif len(obj) == 2 and '__Rational__' in obj: return Rational(*obj['data']) elif len(obj) == 3 and '__RealLiteral__' in obj and 'prec' in obj: return LmfdbRealLiteral(RealField(obj['prec']), obj['data']) elif len(obj) == 2 and '__complex__' in obj: return complex(*obj['data']) elif len(obj) == 3 and '__Complex__' in obj and 'prec' in obj: return ComplexNumber(ComplexField(obj['prec']), *obj['data']) elif len(obj) == 3 and '__NFElt__' in obj and 'parent' in obj: return cls._extract(cls.extract(obj['parent']), obj['data']) elif len(obj) == 3 and ('__NFRelative__' in obj or '__NFAbsolute__' in obj) and 'vname' in obj: poly = cls.extract(obj['data']) return NumberField(poly, name=obj['vname']) elif len(obj) == 2 and '__NFCyclotomic__' in obj: return CyclotomicField(obj['data']) elif len(obj) == 2 and '__IntegerRing__' in obj: return ZZ elif len(obj) == 2 and '__RationalField__' in obj: return QQ elif len(obj) == 3 and '__RationalPoly__' in obj and 'vname' in obj: return QQ[obj['vname']]([QQ(tuple(v)) for v in obj['data']]) elif len(obj) == 4 and '__Poly__' in obj and 'vname' in obj and 'base' in obj: base = cls.extract(obj['base']) return base[obj['vname']]([cls._extract(base, c) for c in obj['data']]) elif len(obj) == 5 and '__PowerSeries__' in obj and 'vname' in obj and 'base' in obj and 'prec' in obj: base = cls.extract(obj['base']) prec = infinity if obj['prec'] == 'inf' else int(obj['prec']) return base[[obj['vname']]]([cls._extract(base, c) for c in obj['data']], prec=prec) elif len(obj) == 2 and '__date__' in obj: return datetime.datetime.strptime(obj['data'], "%Y-%m-%d").date() elif len(obj) == 2 and '__time__' in obj: return datetime.datetime.strptime(obj['data'], "%H:%M:%S.%f").time() elif len(obj) == 2 and '__datetime__' in obj: return datetime.datetime.strptime(obj['data'], "%Y-%m-%d %H:%M:%S.%f") return obj
def sage_character_to_magma(chi,N=None,magma=None): if magma is None: magma = sage.interfaces.magma if N is None: N = chi.modulus() else: N = ZZ(N) chi = chi.extend(N) G = chi.parent() order = chi.order() gens = G.unit_gens() ElGm = magma.DirichletGroupFull(N) Kcyc = CyclotomicField(ElGm.BaseRing().CyclotomicOrder(),names='z') phi = ElGm.BaseRing().sage().hom([Kcyc.gen()]) target = [chi(g) for g in gens] for chim in ElGm.Elements(): if chim.Order().sage() == order: this = [phi(chim.Evaluate(g).sage()) for g in gens] if all((u == v for u, v in zip(this, target))): return chim raise RuntimeError("Should not get to this point")
def eisenstein_series_at_inf(phi, psi, k, prec=10, t=1, base_ring=None): r""" Return Fourier expansion of Eistenstein series at a cusp. INPUT: - ``phi`` -- Dirichlet character. - ``psi`` -- Dirichlet character. - ``k`` -- integer, the weight of the Eistenstein series. - ``prec`` -- integer (default: 10). - ``t`` -- integer (default: 1). OUTPUT: The Fourier expansion of the Eisenstein series $E_k^{\phi,\psi, t}$ (as defined by [Diamond-Shurman]) at the specific cusp. EXAMPLES: sage: phi = DirichletGroup(3)[1] sage: psi = DirichletGroup(5)[1] sage: E = eisenstein_series_at_inf(phi, psi, 4) """ N1, N2 = phi.level(), psi.level() N = N1 * N2 #The Fourier expansion of the Eisenstein series at infinity is in the field Q(zeta_Ncyc) Ncyc = lcm([euler_phi(N1), euler_phi(N2)]) if base_ring == None: base_ring = CyclotomicField(Ncyc) Q = PowerSeriesRing(base_ring, 'q') q = Q.gen() s = O(q**prec) #Weight 2 with trivial characters is calculated separately if k == 2 and phi.conductor() == 1 and psi.conductor() == 1: if t == 1: raise TypeError('E_2 is not a modular form.') s = 1 / 24 * (t - 1) for m in srange(1, prec): for n in srange(1, prec / m): s += n * (q**(m * n) - t * q**(m * n * t)) return s + O(q**prec) if psi.level() == 1 and k == 1: s -= phi.bernoulli(k) / k elif phi.level() == 1: s -= psi.bernoulli(k) / k for m in srange(1, prec / t): for n in srange(1, prec / t / m + 1): s += 2 * base_ring(phi(m)) * base_ring( psi(n)) * n**(k - 1) * q**(m * n * t) return s + O(q**prec)
def eis_E(cv, dv, N, k, Q=None, param_level=1, prec=10): r""" Computes the coefficient of the Eisenstein series for $\Gamma(N)$. Not intended to be called by user. INPUT: - cv - int, the first coordinate of the vector determining the \Gamma(N) Eisenstein series - dv - int, the second coordinate of the vector determining the \Gamma(N) Eisenstein series - N - int, the level of the Eisenstein series to be computed - k - int, the weight of the Eisenstein seriess to be computed - Q - power series ring, the ring containing the q-expansion to be computed - param_level - int, the parameter of the returned series will be q_{param_level} - prec - int, the precision. The series in q_{param_level} will be truncated after prec coefficients OUTPUT: - an element of the ring Q, which is the Fourier expansion of the Eisenstein series """ if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q') R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() cv = cv % N dv = dv % N #if dv == 0 and cv == 0 and k == 2: # raise ValueError("E_2 is not a modular form") if k == 1: if cv == 0 and dv == 0: raise ValueError("that shouldn't have happened...") elif cv == 0 and dv != 0: s = QQ(1) / QQ(2) * (1 + zetaN**dv) / (1 - zetaN**dv) elif cv != 0: s = QQ(1) / QQ(2) - QQ(cv) / QQ(N) + floor(QQ(cv) / QQ(N)) elif k > 1: if cv == 0: s = hurwitz_hat(QQ(dv), QQ(N), 1 - k, zetaN) else: s = 0 for n1 in xrange(1, prec): # this is n/m in DS for n2 in xrange(1, prec / n1 + 1): # this is m in DS if Mod(n1, N) == Mod(cv, N): s += n2**(k - 1) * zetaN**(dv * n2) * q**(n1 * n2) if Mod(n1, N) == Mod(-cv, N): s += (-1)**k * n2**(k - 1) * zetaN**(-dv * n2) * q**(n1 * n2) return s + O(q**floor(prec))
def field(self): r""" Return a cyclotomic field large enough to contain the `2 \ell`-th roots of unity, as well as all the S-matrix entries. EXAMPLES:: sage: FusionRing("A2",2).field() Cyclotomic Field of order 60 and degree 16 sage: FusionRing("B2",2).field() Cyclotomic Field of order 40 and degree 16 """ return CyclotomicField(4 * self._cyclotomic_order)
def eis_F(cv, dv, N, k, Q=None, prec=10, t=1): """ Computes the coefficient of the Eisenstein series for $\Gamma(N)$. Not indented to be called by user. INPUT: - cv - int, the first coordinate of the vector determining the \Gamma(N) Eisenstein series - dv - int, the second coordinate of the vector determining the \Gamma(N) Eisenstein series - N - int, the level of the Eisenstein series to be computed - k - int, the weight of the Eisenstein seriess to be computed - Q - power series ring, the ring containing the q-expansion to be computed - param_level - int, the parameter of the returned series will be q_{param_level} - prec - int, the precision. The series in q_{param_level} will be truncated after prec coefficients OUTPUT: - an element of the ring Q, which is the Fourier expansion of the Eisenstein series """ if Q == None: Q = PowerSeriesRing(CyclotomicField(N), 'q{}'.format(N)) R = Q.base_ring() zetaN = R.zeta(N) q = Q.gen() s = 0 if k == 1: if cv % N == 0 and dv % N != 0: s = QQ(1) / QQ(2) * (1 + zetaN**dv) / (1 - zetaN**dv) elif cv % N != 0: s = QQ(1) / QQ(2) - QQ(cv) / QQ(N) + floor(QQ(cv) / QQ(N)) elif k > 1: s = -ber_pol(QQ(cv) / QQ(N) - floor(QQ(cv) / QQ(N)), k) / QQ(k) for n1 in xrange(1, ceil(prec / QQ(t))): # this is n/m in DS for n2 in xrange(1, ceil(prec / QQ(t) / QQ(n1)) + 1): # this is m in DS if Mod(n1, N) == Mod(cv, N): s += N**(1 - k) * n1**(k - 1) * zetaN**(dv * n2) * q**(t * n1 * n2) if Mod(n1, N) == Mod(-cv, N): s += (-1)**k * N**(1 - k) * n1**(k - 1) * zetaN**( -dv * n2) * q**(t * n1 * n2) return s + O(q**floor(prec))
def toCyclPowerBase(M, order): """ Let's K = CyclotomicField(order). INPUT: - `M` -- A matrix over the cyclomotic field `K`. - `order` -- The order of `K`, the cyclomotic field. OUTPUT: - A list of matrices `ms` in power base where every matrix is a factor to `zeta**i` where `zeta = K.gen()` and `len(ms) == K.degree()`. """ K = CyclotomicField(order) zeta = K.gen() Kcoords = zeta.coordinates_in_terms_of_powers() assert len(K.power_basis()) == K.degree() ms = [matrix(QQ,M.nrows(),M.ncols()) for i in range(K.degree())] for y in range(M.nrows()): for x in range(M.ncols()): try: v_ = M[y,x] v = K(v_) coords = Kcoords(v) except TypeError: print "type of {1} ({2}) is not valid in Cyclomotic field of order {0}".format(order, M[y,x], type(M[y,x])) raise assert len(coords) == K.degree() for i in range(K.degree()): ms[i][y,x] = coords[i] return ms
def _element_constructor_(self, elt): r""" TESTS:: sage: UCF = UniversalCyclotomicField() sage: UCF(3) 3 sage: UCF(3/2) 3/2 sage: C = CyclotomicField(13) sage: UCF(C.gen()) E(13) sage: UCF(C.gen() - 3*C.gen()**2 + 5*C.gen()**5) E(13) - 3*E(13)^2 + 5*E(13)^5 sage: C = CyclotomicField(12) sage: zeta12 = C.gen() sage: a = UCF(zeta12 - 3* zeta12**2) sage: a -E(12)^7 + 3*E(12)^8 sage: C(_) == a True sage: UCF('[[0, 1], [0, 2]]') Traceback (most recent call last): ... TypeError: [ [ 0, 1 ], [ 0, 2 ] ] of type <type 'sage.libs.gap.element.GapElement_List'> not valid to initialize an element of the universal cyclotomic field .. TODO:: Implement conversion from QQbar (and as a consequence from the symbolic ring) """ elt = py_scalar_to_element(elt) if isinstance(elt, (Integer, Rational)): return self.element_class(self, libgap(elt)) elif isinstance(elt, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): return self.element_class(self, elt) elif not elt: return self.zero() obj = None if isinstance(elt, gap.GapElement): obj = libgap(elt) elif isinstance(elt, gap3.GAP3Element): obj = libgap.eval(str(elt)) elif isinstance(elt, str): obj = libgap.eval(elt) if obj is not None: if not isinstance(obj, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): raise TypeError("{} of type {} not valid to initialize an element of the universal cyclotomic field".format(obj, type(obj))) return self.element_class(self, obj) # late import to avoid slowing down the above conversions from sage.rings.number_field.number_field_element import NumberFieldElement from sage.rings.number_field.number_field import NumberField_cyclotomic, CyclotomicField P = parent(elt) if isinstance(elt, NumberFieldElement) and isinstance(P, NumberField_cyclotomic): n = P.gen().multiplicative_order() elt = CyclotomicField(n)(elt) return sum(c * self.gen(n, i) for i, c in enumerate(elt._coefficients())) else: raise TypeError("{} of type {} not valid to initialize an element of the universal cyclotomic field".format(elt, type(elt)))
def attack(m, q, r=4, sigma=3.0, subfield_only=False): K = CyclotomicField(m, 'z') z = K.gen() OK = K.ring_of_integers() G = K.galois_group() n = euler_phi(m) mprime = m / r nprime = euler_phi(mprime) Gprime = [tau for tau in G if tau(z**r) == z**r] R = PolynomialRing(IntegerRing(), 'a') a = R.gen() phim = a**n + 1 D = DiscreteGaussianDistributionIntegerSampler(sigma) print "sampling f,g" while True: f = sum([D() * z**i for i in range(n)]) fx = sum([f[i] * a**i for i in range(n)]) res = inverse(fx, phim, q) if res[0]: f_inv = sum([res[1][i] * z**i for i in range(n)]) print "f_inv * f = %s (mod %d)" % ((f * f_inv).mod(q), q) break g = sum([D() * z**i for i in range(n)]) print "done sampling f, g" #h = [g*f^{-1)]_q h = (g * f_inv).mod(q) lognorm_f = log(f.vector().norm(), 2) lognorm_g = log(g.vector().norm(), 2) print "f*h - g = %s" % (f * h - g).mod(q) print "log q = ", log(q, 2).n(precision) print "log |f| = %s, log |g| = %s" % (lognorm_f.n(precision), lognorm_g.n(precision)) print "log |(f,g)| = ", log( sqrt(f.vector().norm()**2 + g.vector().norm()**2), 2).n(precision) print "begin computing N(f), N(g), N(h), Tr(h), fbar" fprime = norm(f, Gprime) gprime = norm(g, Gprime) hprime = norm(h, Gprime).mod(q) htr = trace(h, Gprime) fbar = prod([tau(f) for tau in Gprime[1:]]) print "end computing N(f), N(g), N(h), Tr(h), fbar" lognorm_fp = log(fprime.vector().norm(), 2) lognorm_gp = log(gprime.vector().norm(), 2) print "%d * log |f| - log |f'| = %s" % (r, r * lognorm_f.n(precision) - lognorm_fp.n(precision)) print "log |(f', g')| = ", log( sqrt(fprime.vector().norm()**2 + gprime.vector().norm()**2), 2).n(precision) print "log |N(f), Tr(g fbar)| = ", log( sqrt(fprime.vector().norm()**2 + trace(g * fbar, Gprime).vector().norm()**2), 2).n(precision) #(fprime, gprime) lies in the lattice \Lambda_hprime^q print "f'*h' - g' = %s " % (hprime * fprime - gprime).mod(q) print "N(f) Tr(h) - Tr(g fbar) = %s" % (htr * fprime - trace(g * fbar, Gprime)).mod(q) if not subfield_only: ntru_full = NTRU(h, K, q) full_sv = ntru_full.shortest_vector() print "log |v| = %s" % log(full_sv.norm(), 2).n(precision) ntru_subfield = NTRU_subfield(hprime, q, nprime, r) ntru_trace_subfield = NTRU_subfield(htr, q, nprime, r) print "begin computing Shortest Vector of subfield lattice" norm_sv = ntru_subfield.shortest_vector() tr_sv = ntru_trace_subfield.shortest_vector() print "end computing Shortest Vector of subfield lattice" norm_xp = sum( [coerce(Integer, norm_sv[i]) * z**(r * i) for i in range(nprime)]) tr_xp = sum( [coerce(Integer, tr_sv[i]) * z**(r * i) for i in range(nprime)]) print "Norm map: log |(x',y')| = ", log(norm_sv.norm(), 2).n(precision) print "Trace map: log |(x', y')| = ", log(tr_sv.norm(), 2).n(precision) #test if xprime belongs to <fprime> mat = [] for i in range(nprime): coordinate = (fprime * z**(r * i)).vector().list() mat.append([coordinate[r * j] for j in range(nprime)]) FL = IntegerLattice(mat) print norm_sv[:nprime] in FL print tr_sv[:nprime] in FL norm_x = norm_xp norm_y = mod_q(norm_x * h, q) tr_x = tr_xp tr_y = mod_q(tr_x * h, q) print "Norm map: log |(x,y)| = ", log( sqrt(norm_x.vector().norm()**2 + norm_y.vector().norm()**2), 2).n(precision) print "Trace map: log |(x,y)| = ", log( sqrt(tr_x.vector().norm()**2 + tr_y.vector().norm()**2), 2).n(precision)
def product_space(chi, k, weights=False, base_ring=None, verbose=False): r""" Computes all eisenstein series, and products of pairs of eisenstein series of lower weight, lying in the space of modular forms of weight $k$ and nebentypus $\chi$. INPUT: - chi - Dirichlet character, the nebentypus of the target space - k - an integer, the weight of the target space OUTPUT: - a matrix of coefficients of q-expansions, which are the products of Eisenstein series in M_k(chi). WARNING: It is only for principal chi that we know that the resulting space is the whole space of modular forms. """ if weights == False: weights = srange(1, k / 2 + 1) weight_dict = {} weight_dict[-1] = [w for w in weights if w % 2] # Odd weights weight_dict[1] = [w for w in weights if not w % 2] # Even weights try: N = chi.modulus() except AttributeError: if chi.parent() == ZZ: N = chi chi = DirichletGroup(N)[0] Id = DirichletGroup(1)[0] if chi(-1) != (-1)**k: raise ValueError('chi(-1)!=(-1)^k') sturm = ModularForms(N, k).sturm_bound() + 1 if N > 1: target_dim = dimension_modular_forms(chi, k) else: target_dim = dimension_modular_forms(1, k) D = DirichletGroup(N) # product_space should ideally be called over number fields. Over complex # numbers the exact linear algebra solutions might not exist. if base_ring == None: base_ring = CyclotomicField(euler_phi(N)) Q = PowerSeriesRing(base_ring, 'q') q = Q.gen() d = len(D) prim_chars = [phi.primitive_character() for phi in D] divs = divisors(N) products = Matrix(base_ring, []) indexlist = [] rank = 0 if verbose: print(D) print('Sturm bound', sturm) #TODO: target_dim needs refinment in the case of weight 2. print('Target dimension', target_dim) for i in srange(0, d): # First character phi = prim_chars[i] M1 = phi.conductor() for j in srange(0, d): # Second character psi = prim_chars[j] M2 = psi.conductor() if not M1 * M2 in divs: continue parity = psi(-1) * phi(-1) for t1 in divs: if not M1 * M2 * t1 in divs: continue #TODO: THE NEXT CONDITION NEEDS TO BE CORRECTED. THIS IS JUST A TEST if phi.bar() == psi and not ( k == 2): #and i==0 and j==0 and t1==1): E = eisenstein_series_at_inf(phi, psi, k, sturm, t1, base_ring).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([k, i, j, t1]) rank += 1 if verbose: print('Added ', [k, i, j, t1]) print('Rank is now', rank) if rank == target_dim: return products, indexlist for t in divs: if not M1 * M2 * t1 * t in divs: continue for t2 in divs: if not M1 * M2 * t1 * t2 * t in divs: continue for l in weight_dict[parity]: if l == 1 and phi.is_odd(): continue if i == 0 and j == 0 and (l == 2 or l == k - 2): continue #TODO: THE NEXT CONDITION NEEDS TO BE REMOVED. THIS IS JUST A TEST if l == 2 or l == k - 2: continue E1 = eisenstein_series_at_inf( phi, psi, l, sturm, t1 * t, base_ring) E2 = eisenstein_series_at_inf( phi**(-1), psi**(-1), k - l, sturm, t2 * t, base_ring) #If chi is non-principal this needs to be changed to be something like chi*phi^(-1) instead of phi^(-1) E = (E1 * E2 + O(q**sturm)).padded_list() try: products.T.solve_right(vector(base_ring, E)) except ValueError: products = Matrix(products.rows() + [E]) indexlist.append([l, k - l, i, j, t1, t2, t]) rank += 1 if verbose: print('Added ', [l, k - l, i, j, t1, t2, t]) print('Rank', rank) if rank == target_dim: return products, indexlist return products, indexlist
def _element_constructor_(self, elt): r""" TESTS:: sage: UCF = UniversalCyclotomicField() sage: UCF(3) 3 sage: UCF(3/2) 3/2 sage: C = CyclotomicField(13) sage: UCF(C.gen()) E(13) sage: UCF(C.gen() - 3*C.gen()**2 + 5*C.gen()**5) E(13) - 3*E(13)^2 + 5*E(13)^5 sage: C = CyclotomicField(12) sage: zeta12 = C.gen() sage: a = UCF(zeta12 - 3* zeta12**2) sage: a -E(12)^7 + 3*E(12)^8 sage: C(_) == a True sage: UCF('[[0, 1], [0, 2]]') Traceback (most recent call last): ... TypeError: [ [ 0, 1 ], [ 0, 2 ] ] of type <type 'sage.libs.gap.element.GapElement_List'> not valid to initialize an element of the universal cyclotomic field .. TODO:: Implement conversion from QQbar (and as a consequence from the symbolic ring) """ elt = py_scalar_to_element(elt) if isinstance(elt, (Integer, Rational)): return self.element_class(self, libgap(elt)) elif isinstance(elt, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): return self.element_class(self, elt) elif not elt: return self.zero() obj = None if isinstance(elt, gap.GapElement): obj = libgap(elt) elif isinstance(elt, gap3.GAP3Element): obj = libgap.eval(str(elt)) elif isinstance(elt, str): obj = libgap.eval(elt) if obj is not None: if not isinstance(obj, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): raise TypeError("{} of type {} not valid to initialize an element of the universal cyclotomic field".format(obj, type(obj))) return self.element_class(self, obj) # late import to avoid slowing down the above conversions from sage.rings.number_field.number_field_element import NumberFieldElement from sage.rings.number_field.number_field import NumberField_cyclotomic, CyclotomicField P = parent(elt) if isinstance(elt, NumberFieldElement) and isinstance(P, NumberField_cyclotomic): n = P.gen().multiplicative_order() elt = CyclotomicField(n)(elt) coeffs = elt._coefficients() return sum(c * self.gen(n,i) for i,c in enumerate(elt._coefficients())) else: raise TypeError("{} of type {} not valid to initialize an element of the universal cyclotomic field".format(elt, type(elt)))
def dft(self, chi = lambda x: x): """ A discrete Fourier transform "over `\QQ`" using exact `N`-th roots of unity. EXAMPLES:: sage: J = range(6) sage: A = [ZZ(1) for i in J] sage: s = IndexedSequence(A,J) sage: s.dft(lambda x:x^2) Indexed sequence: [6, 0, 0, 6, 0, 0] indexed by [0, 1, 2, 3, 4, 5] sage: s.dft() Indexed sequence: [6, 0, 0, 0, 0, 0] indexed by [0, 1, 2, 3, 4, 5] sage: G = SymmetricGroup(3) sage: J = G.conjugacy_classes_representatives() sage: s = IndexedSequence([1,2,3],J) # 1,2,3 are the values of a class fcn on G sage: s.dft() # the "scalar-valued Fourier transform" of this class fcn Indexed sequence: [8, 2, 2] indexed by [(), (1,2), (1,2,3)] sage: J = AbelianGroup(2,[2,3],names='ab') sage: s = IndexedSequence([1,2,3,4,5,6],J) sage: s.dft() # the precision of output is somewhat random and architecture dependent. Indexed sequence: [21.0000000000000, -2.99999999999997 - 1.73205080756885*I, -2.99999999999999 + 1.73205080756888*I, -9.00000000000000 + 0.0000000000000485744257349999*I, -0.00000000000000976996261670137 - 0.0000000000000159872115546022*I, -0.00000000000000621724893790087 - 0.0000000000000106581410364015*I] indexed by Multiplicative Abelian group isomorphic to C2 x C3 sage: J = CyclicPermutationGroup(6) sage: s = IndexedSequence([1,2,3,4,5,6],J) sage: s.dft() # the precision of output is somewhat random and architecture dependent. Indexed sequence: [21.0000000000000, -2.99999999999997 - 1.73205080756885*I, -2.99999999999999 + 1.73205080756888*I, -9.00000000000000 + 0.0000000000000485744257349999*I, -0.00000000000000976996261670137 - 0.0000000000000159872115546022*I, -0.00000000000000621724893790087 - 0.0000000000000106581410364015*I] indexed by Cyclic group of order 6 as a permutation group sage: p = 7; J = range(p); A = [kronecker_symbol(j,p) for j in J] sage: s = IndexedSequence(A,J) sage: Fs = s.dft() sage: c = Fs.list()[1]; [x/c for x in Fs.list()]; s.list() [0, 1, 1, -1, 1, -1, -1] [0, 1, 1, -1, 1, -1, -1] The DFT of the values of the quadratic residue symbol is itself, up to a constant factor (denoted c on the last line above). .. TODO:: Read the parent of the elements of S; if `\QQ` or `\CC` leave as is; if AbelianGroup, use abelian_group_dual; if some other implemented Group (permutation, matrix), call .characters() and test if the index list is the set of conjugacy classes. """ J = self.index_object() ## index set of length N N = len(J) S = self.list() F = self.base_ring() ## elements must be coercible into QQ(zeta_N) if not(J[0] in ZZ): G = J[0].parent() ## if J is not a range it is a group G if J[0] in ZZ and F.base_ring().fraction_field()==QQ: ## assumes J is range(N) zeta = CyclotomicField(N).gen() FT = [sum([S[i]*chi(zeta**(i*j)) for i in J]) for j in J] elif not(J[0] in ZZ) and G.is_abelian() and F == ZZ or (F.is_field() and F.base_ring()==QQ): if is_PermutationGroupElement(J[0]): ## J is a CyclicPermGp n = G.order() a = list(factor(n)) invs = [x[0]**x[1] for x in a] G = AbelianGroup(len(a),invs) ## assumes J is AbelianGroup(...) Gd = G.dual_group() FT = [sum([S[i]*chid(G.list()[i]) for i in range(N)]) for chid in Gd] elif not(J[0] in ZZ) and G.is_finite() and F == ZZ or (F.is_field() and F.base_ring()==QQ): ## assumes J is the list of conj class representatives of a ## PermuationGroup(...) or Matrixgroup(...) chi = G.character_table() FT = [sum([S[i]*chi[i,j] for i in range(N)]) for j in range(N)] else: raise ValueError("list elements must be in QQ(zeta_"+str(N)+")") return IndexedSequence(FT,J)
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 Omega_ge(a, exponents): r""" Return `\Omega_{\ge}` of the expression specified by the input. To be more precise, calculate .. MATH:: \Omega_{\ge} \frac{\mu^a}{ (1 - z_0 \mu^{e_0}) \dots (1 - z_{n-1} \mu^{e_{n-1}})} and return its numerator and a factorization of its denominator. Note that `z_0`, ..., `z_{n-1}` only appear in the output, but not in the input. INPUT: - ``a`` -- an integer - ``exponents`` -- a tuple of integers OUTPUT: A pair representing a quotient as follows: Its first component is the numerator as a Laurent polynomial, its second component a factorization of the denominator as a tuple of Laurent polynomials, where each Laurent polynomial `z` represents a factor `1 - z`. The parents of these Laurent polynomials is always a Laurent polynomial ring in `z_0`, ..., `z_{n-1}` over `\ZZ`, where `n` is the length of ``exponents``. EXAMPLES:: sage: from sage.rings.polynomial.omega import Omega_ge sage: Omega_ge(0, (1, -2)) (1, (z0, z0^2*z1)) sage: Omega_ge(0, (1, -3)) (1, (z0, z0^3*z1)) sage: Omega_ge(0, (1, -4)) (1, (z0, z0^4*z1)) sage: Omega_ge(0, (2, -1)) (z0*z1 + 1, (z0, z0*z1^2)) sage: Omega_ge(0, (3, -1)) (z0*z1^2 + z0*z1 + 1, (z0, z0*z1^3)) sage: Omega_ge(0, (4, -1)) (z0*z1^3 + z0*z1^2 + z0*z1 + 1, (z0, z0*z1^4)) sage: Omega_ge(0, (1, 1, -2)) (-z0^2*z1*z2 - z0*z1^2*z2 + z0*z1*z2 + 1, (z0, z1, z0^2*z2, z1^2*z2)) sage: Omega_ge(0, (2, -1, -1)) (z0*z1*z2 + z0*z1 + z0*z2 + 1, (z0, z0*z1^2, z0*z2^2)) sage: Omega_ge(0, (2, 1, -1)) (-z0*z1*z2^2 - z0*z1*z2 + z0*z2 + 1, (z0, z1, z0*z2^2, z1*z2)) :: sage: Omega_ge(0, (2, -2)) (-z0*z1 + 1, (z0, z0*z1, z0*z1)) sage: Omega_ge(0, (2, -3)) (z0^2*z1 + 1, (z0, z0^3*z1^2)) sage: Omega_ge(0, (3, 1, -3)) (-z0^3*z1^3*z2^3 + 2*z0^2*z1^3*z2^2 - z0*z1^3*z2 + z0^2*z2^2 - 2*z0*z2 + 1, (z0, z1, z0*z2, z0*z2, z0*z2, z1^3*z2)) :: sage: Omega_ge(0, (3, 6, -1)) (-z0*z1*z2^8 - z0*z1*z2^7 - z0*z1*z2^6 - z0*z1*z2^5 - z0*z1*z2^4 + z1*z2^5 - z0*z1*z2^3 + z1*z2^4 - z0*z1*z2^2 + z1*z2^3 - z0*z1*z2 + z0*z2^2 + z1*z2^2 + z0*z2 + z1*z2 + 1, (z0, z1, z0*z2^3, z1*z2^6)) TESTS:: sage: Omega_ge(0, (2, 2, 1, 1, 1, -1, -1))[0].number_of_terms() # long time 1695 sage: Omega_ge(0, (2, 2, 1, 1, 1, 1, 1, -1, -1))[0].number_of_terms() # not tested (too long, 1 min) 27837 :: sage: Omega_ge(1, (2,)) (1, (z0,)) """ import logging logger = logging.getLogger(__name__) logger.info('Omega_ge: a=%s, exponents=%s', a, exponents) from sage.arith.all import lcm, srange from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.number_field.number_field import CyclotomicField if not exponents or any(e == 0 for e in exponents): raise NotImplementedError rou = sorted(set(abs(e) for e in exponents) - set([1])) ellcm = lcm(rou) B = CyclotomicField(ellcm, 'zeta') zeta = B.gen() z_names = tuple('z{}'.format(i) for i in range(len(exponents))) L = LaurentPolynomialRing(B, ('t', ) + z_names, len(z_names) + 1) t = L.gens()[0] Z = LaurentPolynomialRing(ZZ, z_names, len(z_names)) powers = {i: L(zeta**(ellcm // i)) for i in rou} powers[2] = L(-1) powers[1] = L(1) exponents_and_values = tuple( (e, tuple(powers[abs(e)]**j * z for j in srange(abs(e)))) for z, e in zip(L.gens()[1:], exponents)) x = tuple(v for e, v in exponents_and_values if e > 0) y = tuple(v for e, v in exponents_and_values if e < 0) def subs_power(expression, var, exponent): r""" Substitute ``var^exponent`` by ``var`` in ``expression``. It is assumed that ``var`` only occurs with exponents divisible by ``exponent``. """ p = tuple(var.dict().popitem()[0]).index( 1) # var is the p-th generator def subs_e(e): e = list(e) assert e[p] % exponent == 0 e[p] = e[p] // exponent return tuple(e) parent = expression.parent() result = parent( {subs_e(e): c for e, c in iteritems(expression.dict())}) return result def de_power(expression): expression = Z(expression) for e, var in zip(exponents, Z.gens()): if abs(e) == 1: continue expression = subs_power(expression, var, abs(e)) return expression logger.debug('Omega_ge: preparing denominator') factors_denominator = tuple( de_power(1 - factor) for factor in _Omega_factors_denominator_(x, y)) logger.debug('Omega_ge: preparing numerator') numerator = de_power(_Omega_numerator_(a, x, y, t)) logger.info('Omega_ge: completed') return numerator, factors_denominator
def Omega_ge(a, exponents): r""" Return `\Omega_{\ge}` of the expression specified by the input. To be more precise, calculate .. MATH:: \Omega_{\ge} \frac{\mu^a}{ (1 - z_0 \mu^{e_0}) \dots (1 - z_{n-1} \mu^{e_{n-1}})} and return its numerator and a factorization of its denominator. Note that `z_0`, ..., `z_{n-1}` only appear in the output, but not in the input. INPUT: - ``a`` -- an integer - ``exponents`` -- a tuple of integers OUTPUT: A pair representing a quotient as follows: Its first component is the numerator as a Laurent polynomial, its second component a factorization of the denominator as a tuple of Laurent polynomials, where each Laurent polynomial `z` represents a factor `1 - z`. The parents of these Laurent polynomials is always a Laurent polynomial ring in `z_0`, ..., `z_{n-1}` over `\ZZ`, where `n` is the length of ``exponents``. EXAMPLES:: sage: from sage.rings.polynomial.omega import Omega_ge sage: Omega_ge(0, (1, -2)) (1, (z0, z0^2*z1)) sage: Omega_ge(0, (1, -3)) (1, (z0, z0^3*z1)) sage: Omega_ge(0, (1, -4)) (1, (z0, z0^4*z1)) sage: Omega_ge(0, (2, -1)) (z0*z1 + 1, (z0, z0*z1^2)) sage: Omega_ge(0, (3, -1)) (z0*z1^2 + z0*z1 + 1, (z0, z0*z1^3)) sage: Omega_ge(0, (4, -1)) (z0*z1^3 + z0*z1^2 + z0*z1 + 1, (z0, z0*z1^4)) sage: Omega_ge(0, (1, 1, -2)) (-z0^2*z1*z2 - z0*z1^2*z2 + z0*z1*z2 + 1, (z0, z1, z0^2*z2, z1^2*z2)) sage: Omega_ge(0, (2, -1, -1)) (z0*z1*z2 + z0*z1 + z0*z2 + 1, (z0, z0*z1^2, z0*z2^2)) sage: Omega_ge(0, (2, 1, -1)) (-z0*z1*z2^2 - z0*z1*z2 + z0*z2 + 1, (z0, z1, z0*z2^2, z1*z2)) :: sage: Omega_ge(0, (2, -2)) (-z0*z1 + 1, (z0, z0*z1, z0*z1)) sage: Omega_ge(0, (2, -3)) (z0^2*z1 + 1, (z0, z0^3*z1^2)) sage: Omega_ge(0, (3, 1, -3)) (-z0^3*z1^3*z2^3 + 2*z0^2*z1^3*z2^2 - z0*z1^3*z2 + z0^2*z2^2 - 2*z0*z2 + 1, (z0, z1, z0*z2, z0*z2, z0*z2, z1^3*z2)) :: sage: Omega_ge(0, (3, 6, -1)) (-z0*z1*z2^8 - z0*z1*z2^7 - z0*z1*z2^6 - z0*z1*z2^5 - z0*z1*z2^4 + z1*z2^5 - z0*z1*z2^3 + z1*z2^4 - z0*z1*z2^2 + z1*z2^3 - z0*z1*z2 + z0*z2^2 + z1*z2^2 + z0*z2 + z1*z2 + 1, (z0, z1, z0*z2^3, z1*z2^6)) TESTS:: sage: Omega_ge(0, (2, 2, 1, 1, 1, 1, 1, -1, -1))[0].number_of_terms() # long time 27837 :: sage: Omega_ge(1, (2,)) (1, (z0,)) """ import logging logger = logging.getLogger(__name__) logger.info('Omega_ge: a=%s, exponents=%s', a, exponents) from sage.arith.all import lcm, srange from sage.rings.integer_ring import ZZ from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing from sage.rings.number_field.number_field import CyclotomicField if not exponents or any(e == 0 for e in exponents): raise NotImplementedError rou = sorted(set(abs(e) for e in exponents) - set([1])) ellcm = lcm(rou) B = CyclotomicField(ellcm, 'zeta') zeta = B.gen() z_names = tuple('z{}'.format(i) for i in range(len(exponents))) L = LaurentPolynomialRing(B, ('t',) + z_names, len(z_names) + 1) t = L.gens()[0] Z = LaurentPolynomialRing(ZZ, z_names, len(z_names)) powers = {i: L(zeta**(ellcm//i)) for i in rou} powers[2] = L(-1) powers[1] = L(1) exponents_and_values = tuple( (e, tuple(powers[abs(e)]**j * z for j in srange(abs(e)))) for z, e in zip(L.gens()[1:], exponents)) x = tuple(v for e, v in exponents_and_values if e > 0) y = tuple(v for e, v in exponents_and_values if e < 0) def subs_power(expression, var, exponent): r""" Substitute ``var^exponent`` by ``var`` in ``expression``. It is assumed that ``var`` only occurs with exponents divisible by ``exponent``. """ p = tuple(var.dict().popitem()[0]).index(1) # var is the p-th generator def subs_e(e): e = list(e) assert e[p] % exponent == 0 e[p] = e[p] // exponent return tuple(e) parent = expression.parent() result = parent({subs_e(e): c for e, c in iteritems(expression.dict())}) return result def de_power(expression): expression = Z(expression) for e, var in zip(exponents, Z.gens()): if abs(e) == 1: continue expression = subs_power(expression, var, abs(e)) return expression logger.debug('Omega_ge: preparing denominator') factors_denominator = tuple(de_power(1 - factor) for factor in _Omega_factors_denominator_(x, y)) logger.debug('Omega_ge: preparing numerator') numerator = de_power(_Omega_numerator_(a, x, y, t)) logger.info('Omega_ge: completed') return numerator, factors_denominator
def _element_constructor_(self, elt): r""" TESTS:: sage: UCF = UniversalCyclotomicField() sage: UCF(3) 3 sage: UCF(3/2) 3/2 sage: C = CyclotomicField(13) sage: UCF(C.gen()) E(13) sage: UCF(C.gen() - 3*C.gen()**2 + 5*C.gen()**5) E(13) - 3*E(13)^2 + 5*E(13)^5 sage: C = CyclotomicField(12) sage: zeta12 = C.gen() sage: a = UCF(zeta12 - 3* zeta12**2) sage: a -E(12)^7 + 3*E(12)^8 sage: C(_) == a True sage: UCF('[[0, 1], [0, 2]]') Traceback (most recent call last): ... TypeError: [ [ 0, 1 ], [ 0, 2 ] ] of type <type 'sage.libs.gap.element.GapElement_List'> not valid to initialize an element of the universal cyclotomic field Some conversions from symbolic functions are possible:: sage: UCF = UniversalCyclotomicField() sage: [UCF(sin(pi/k, hold=True)) for k in range(1,10)] [0, 1, -1/2*E(12)^7 + 1/2*E(12)^11, 1/2*E(8) - 1/2*E(8)^3, -1/2*E(20)^13 + 1/2*E(20)^17, 1/2, -1/2*E(28)^19 + 1/2*E(28)^23, 1/2*E(16)^3 - 1/2*E(16)^5, -1/2*E(36)^25 + 1/2*E(36)^29] sage: [UCF(cos(pi/k, hold=True)) for k in range(1,10)] [-1, 0, 1/2, 1/2*E(8) - 1/2*E(8)^3, -1/2*E(5)^2 - 1/2*E(5)^3, -1/2*E(12)^7 + 1/2*E(12)^11, -1/2*E(7)^3 - 1/2*E(7)^4, 1/2*E(16) - 1/2*E(16)^7, -1/2*E(9)^4 - 1/2*E(9)^5] .. TODO:: Implement conversion from QQbar (and as a consequence from the symbolic ring) """ elt = py_scalar_to_element(elt) if isinstance(elt, (Integer, Rational)): return self.element_class(self, libgap(elt)) elif isinstance( elt, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): return self.element_class(self, elt) elif not elt: return self.zero() obj = None if isinstance(elt, gap.GapElement): obj = libgap(elt) elif isinstance(elt, gap3.GAP3Element): obj = libgap.eval(str(elt)) elif isinstance(elt, str): obj = libgap.eval(elt) if obj is not None: if not isinstance(obj, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): raise TypeError( "{} of type {} not valid to initialize an element of the universal cyclotomic field" .format(obj, type(obj))) return self.element_class(self, obj) # late import to avoid slowing down the above conversions from sage.rings.number_field.number_field_element import NumberFieldElement from sage.rings.number_field.number_field import NumberField_cyclotomic, CyclotomicField P = parent(elt) if isinstance(elt, NumberFieldElement) and isinstance( P, NumberField_cyclotomic): n = P.gen().multiplicative_order() elt = CyclotomicField(n)(elt) return sum(c * self.gen(n, i) for i, c in enumerate(elt._coefficients())) if hasattr(elt, '_algebraic_'): return elt._algebraic_(self) raise TypeError( "{} of type {} not valid to initialize an element of the universal cyclotomic field" .format(elt, type(elt)))
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