def guard_bits(dop, maj, pt, ordrec, nterms): r""" Helper for choosing a working precision. This is done under the assumption that the first terms of the coefficient sequence are computed in interval arithmetic, and then, starting from some cutoff index, we switch to something like floating-point arithmetic with an rounding error bound computed on the side. This function returns a suggested cutoff index and a corresponding number of guard bits to add to the precision of the output. The computation done by this function is heuristic, but the output does not affect the correctness of the final result (only its sharpness and/or the computation time). The algorithm is based on what we can expect to happen at an ordinary point and may or may not work in the regular singular case. """ new_cost = cur_cost = sys.maxsize new_bits = cur_bits = None new_n0 = cur_n0 = orddeq = dop.order() refine = False cst = abs(bounds.IC(maj.dop.leading_coefficient()[0])) # ??? while True: # Roughly speaking, the computation of a new coefficient of the series # *multiplies* the diameter by the order of the recurrence (minus two). # Thus, it is not unreasonable that the loss of precision is of the # order of log2(ordrec^nterms). This observation is far from explaining # everything, though; in particular, it completely ignores the size of # the coefficients. Anyhow, this formula seems to work reasonaly well in # practice. It is perhaps a bit pessimistic for simple equations. guard_bits_intervals = new_n0*max(1, ZZ(ordrec - 2).nbits()) # est_rnd_err = rough estimate of global round-off error # ≈ (local error for a single term) × (propagation factor) # ≈ (ordrec × working prec epsilon) × (value of majorant series) rnd_maj = maj(new_n0) rnd_maj >>= new_n0 est_lg_rnd_fac = (cst*rnd_maj.bound(pt.rad, rows=orddeq)).log(2) est_lg_rnd_err = 2*bounds.IR(ordrec + 1).log(2) if not est_lg_rnd_fac < bounds.IR.zero(): est_lg_rnd_err += est_lg_rnd_fac if est_lg_rnd_fac.is_finite(): guard_bits_squashed = int(est_lg_rnd_err.ceil().upper()) + 2 else: guard_bits_squashed = sys.maxsize # We expect the effective working precision to decrease linearly in the # first phase due to interval blow-up, and then stabilize around (target # prec + guard_bits_squashed). new_cost = (new_n0//2)*guard_bits_intervals + nterms*guard_bits_squashed new_bits = guard_bits_intervals + guard_bits_squashed logger.debug( "n0 = %s, terms = %s, guard bits = %s+%s = %s, cost = %s", new_n0, nterms, guard_bits_intervals, guard_bits_squashed, new_bits, new_cost) if cur_cost <= new_cost < sys.maxsize: return cur_n0, cur_bits if (refine and maj.can_refine() and guard_bits_squashed > guard_bits_intervals + 50): maj.refine() else: new_n0, cur_n0 = new_n0*2, new_n0 cur_cost = new_cost cur_bits = new_bits refine = not refine if new_n0 > nterms: return nterms, guard_bits_intervals
def to_sage(self): """ EXAMPLES:: sage: macaulay2(ZZ).to_sage() # optional - macaulay2 Integer Ring sage: macaulay2(QQ).to_sage() # optional - macaulay2 Rational Field sage: macaulay2(2).to_sage() # optional - macaulay2 2 sage: macaulay2(1/2).to_sage() # optional - macaulay2 1/2 sage: macaulay2(2/1).to_sage() # optional - macaulay2 2 sage: _.parent() # optional - macaulay2 Rational Field sage: macaulay2([1,2,3]).to_sage() # optional - macaulay2 [1, 2, 3] sage: m = matrix([[1,2],[3,4]]) sage: macaulay2(m).to_sage() # optional - macaulay2 [1 2] [3 4] sage: macaulay2(QQ['x,y']).to_sage() # optional - macaulay2 Multivariate Polynomial Ring in x, y over Rational Field sage: macaulay2(QQ['x']).to_sage() # optional - macaulay2 Univariate Polynomial Ring in x over Rational Field sage: macaulay2(GF(7)['x,y']).to_sage() # optional - macaulay2 Multivariate Polynomial Ring in x, y over Finite Field of size 7 sage: macaulay2(GF(7)).to_sage() # optional - macaulay2 Finite Field of size 7 sage: macaulay2(GF(49, 'a')).to_sage() # optional - macaulay2 Finite Field in a of size 7^2 sage: R.<x,y> = QQ[] sage: macaulay2(x^2+y^2+1).to_sage() # optional - macaulay2 x^2 + y^2 + 1 sage: R = macaulay2("QQ[x,y]") # optional - macaulay2 sage: I = macaulay2("ideal (x,y)") # optional - macaulay2 sage: I.to_sage() # optional - macaulay2 Ideal (x, y) of Multivariate Polynomial Ring in x, y over Rational Field sage: X = R/I # optional - macaulay2 sage: X.to_sage() # optional - macaulay2 Quotient of Multivariate Polynomial Ring in x, y over Rational Field by the ideal (x, y) sage: R = macaulay2("QQ^2") # optional - macaulay2 sage: R.to_sage() # optional - macaulay2 Vector space of dimension 2 over Rational Field sage: m = macaulay2('"hello"') # optional - macaulay2 sage: m.to_sage() # optional - macaulay2 'hello' """ repr_str = str(self) cls_str = str(self.cls()) cls_cls_str = str(self.cls().cls()) if repr_str == "ZZ": from sage.rings.all import ZZ return ZZ elif repr_str == "QQ": from sage.rings.all import QQ return QQ if cls_cls_str == "Type": if cls_str == "List": return [entry.to_sage() for entry in self] elif cls_str == "Matrix": from sage.matrix.all import matrix base_ring = self.ring().to_sage() entries = self.entries().to_sage() return matrix(base_ring, entries) elif cls_str == "Ideal": parent = self.ring().to_sage() gens = self.gens().entries().flatten().to_sage() return parent.ideal(*gens) elif cls_str == "QuotientRing": #Handle the ZZ/n case if "ZZ" in repr_str and "--" in repr_str: from sage.rings.all import ZZ, GF external_string = self.external_string() zz, n = external_string.split("/") #Note that n must be prime since it is #coming from Macaulay 2 return GF(ZZ(n)) ambient = self.ambient().to_sage() ideal = self.ideal().to_sage() return ambient.quotient(ideal) elif cls_str == "PolynomialRing": from sage.rings.all import PolynomialRing from sage.rings.polynomial.term_order import inv_macaulay2_name_mapping #Get the base ring base_ring = self.coefficientRing().to_sage() #Get a string list of generators gens = str(self.gens())[1:-1] # Check that we are dealing with default degrees, i.e. 1's. if self.degrees().any("x -> x != {1}").to_sage(): raise ValueError( "cannot convert Macaulay2 polynomial ring with non-default degrees to Sage" ) #Handle the term order external_string = self.external_string() order = None if "MonomialOrder" not in external_string: order = "degrevlex" else: for order_name in inv_macaulay2_name_mapping: if order_name in external_string: order = inv_macaulay2_name_mapping[order_name] if len(gens) > 1 and order is None: raise ValueError( "cannot convert Macaulay2's term order to a Sage term order" ) return PolynomialRing(base_ring, order=order, names=gens) elif cls_str == "GaloisField": from sage.rings.all import ZZ, GF gf, n = repr_str.split(" ") n = ZZ(n) if n.is_prime(): return GF(n) else: gen = str(self.gens())[1:-1] return GF(n, gen) elif cls_str == "Boolean": if repr_str == "true": return True elif repr_str == "false": return False elif cls_str == "String": return str(repr_str) elif cls_str == "Module": from sage.modules.all import FreeModule if self.isFreeModule().to_sage(): ring = self.ring().to_sage() rank = self.rank().to_sage() return FreeModule(ring, rank) else: #Handle the integers and rationals separately if cls_str == "ZZ": from sage.rings.all import ZZ return ZZ(repr_str) elif cls_str == "QQ": from sage.rings.all import QQ repr_str = self.external_string() if "/" not in repr_str: repr_str = repr_str + "/1" return QQ(repr_str) m2_parent = self.cls() parent = m2_parent.to_sage() if cls_cls_str == "PolynomialRing": from sage.misc.sage_eval import sage_eval gens_dict = parent.gens_dict() return sage_eval(self.external_string(), gens_dict) from sage.misc.sage_eval import sage_eval try: return sage_eval(repr_str) except Exception: raise NotImplementedError("cannot convert %s to a Sage object" % repr_str)
def make_conductor(ecnfdata, hfield): N, c, d = [ZZ(c) for c in ecnfdata['conductor_ideal'][1:-1].split(',')] return hfield.K().ideal([N // d, c + d * hfield.K().gen()])
def __init__(self, a, b=None, parent=None, check=True): r""" Create the cusp a/b in `\mathbb{P}^1(\QQ)`, where if b=0 this is the cusp at infinity. When present, b must either be Infinity or coercible to an Integer. EXAMPLES:: sage: Cusp(2,3) 2/3 sage: Cusp(3,6) 1/2 sage: Cusp(1,0) Infinity sage: Cusp(infinity) Infinity sage: Cusp(5) 5 sage: Cusp(1/2) 1/2 sage: Cusp(1.5) 3/2 sage: Cusp(int(7)) 7 sage: Cusp(1, 2, check=False) 1/2 sage: Cusp('sage', 2.5, check=False) # don't do this! sage/2.50000000000000 :: sage: I**2 -1 sage: Cusp(I) Traceback (most recent call last): ... TypeError: unable to convert I to a cusp :: sage: a = Cusp(2,3) sage: loads(a.dumps()) == a True :: sage: Cusp(1/3,0) Infinity sage: Cusp((1,0)) Infinity TESTS:: sage: Cusp("1/3", 5) 1/15 sage: Cusp(Cusp(3/5), 7) 3/35 sage: Cusp(5/3, 0) Infinity sage: Cusp(3,oo) 0 sage: Cusp((7,3), 5) 7/15 sage: Cusp(int(5), 7) 5/7 :: sage: Cusp(0,0) Traceback (most recent call last): ... TypeError: unable to convert (0, 0) to a cusp :: sage: Cusp(oo,oo) Traceback (most recent call last): ... TypeError: unable to convert (+Infinity, +Infinity) to a cusp :: sage: Cusp(Cusp(oo),oo) Traceback (most recent call last): ... TypeError: unable to convert (Infinity, +Infinity) to a cusp """ if parent is None: parent = Cusps Element.__init__(self, parent) if not check: self.__a = a self.__b = b return if b is None: if isinstance(a, Integer): self.__a = a self.__b = ZZ.one() elif isinstance(a, Rational): self.__a = a.numer() self.__b = a.denom() elif is_InfinityElement(a): self.__a = ZZ.one() self.__b = ZZ.zero() elif isinstance(a, Cusp): self.__a = a.__a self.__b = a.__b elif isinstance(a, integer_types): self.__a = ZZ(a) self.__b = ZZ.one() elif isinstance(a, (tuple, list)): if len(a) != 2: raise TypeError("unable to convert %r to a cusp" % a) if ZZ(a[1]) == 0: self.__a = ZZ.one() self.__b = ZZ.zero() return try: r = QQ((a[0], a[1])) self.__a = r.numer() self.__b = r.denom() except (ValueError, TypeError): raise TypeError("unable to convert %r to a cusp" % a) else: try: r = QQ(a) self.__a = r.numer() self.__b = r.denom() except (ValueError, TypeError): raise TypeError("unable to convert %r to a cusp" % a) return if is_InfinityElement(b): if is_InfinityElement(a) or (isinstance(a, Cusp) and a.is_infinity()): raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) self.__a = ZZ.zero() self.__b = ZZ.one() return elif not b: if not a: raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) self.__a = ZZ.one() self.__b = ZZ.zero() return if isinstance(a, Integer) or isinstance(a, Rational): r = a / ZZ(b) elif is_InfinityElement(a): self.__a = ZZ.one() self.__b = ZZ.zero() return elif isinstance(a, Cusp): if a.__b: r = a.__a / (a.__b * b) else: self.__a = ZZ.one() self.__b = ZZ.zero() return elif isinstance(a, integer_types): r = ZZ(a) / b elif isinstance(a, (tuple, list)): if len(a) != 2: raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) r = ZZ(a[0]) / (ZZ(a[1]) * b) else: try: r = QQ(a) / b except (ValueError, TypeError): raise TypeError("unable to convert (%r, %r) to a cusp" % (a, b)) self.__a = r.numer() self.__b = r.denom()
def q_binomial(n, k, q=None, algorithm='auto'): r""" Return the `q`-binomial coefficient. This is also known as the Gaussian binomial coefficient, and is defined by .. MATH:: \binom{n}{k}_q = \frac{(1-q^n)(1-q^{n-1}) \cdots (1-q^{n-k+1})} {(1-q)(1-q^2)\cdots (1-q^k)}. See :wikipedia:`Gaussian_binomial_coefficient`. If `q` is unspecified, then the variable is the generator `q` for a univariate polynomial ring over the integers. INPUT: - ``n, k`` -- the values `n` and `k` defined above - ``q`` -- (default: ``None``) the variable `q`; if ``None``, then use a default variable in `\ZZ[q]` - ``algorithm`` -- (default: ``'auto'``) the algorithm to use and can be one of the following: - ``'auto'`` -- automatically choose the algorithm; see the algorithm section below - ``'naive'`` -- use the naive algorithm - ``'cyclotomic'`` -- use cyclotomic algorithm ALGORITHM: The naive algorithm uses the product formula. The cyclotomic algorithm uses a product of cyclotomic polynomials (cf. [CH2006]_). When the algorithm is set to ``'auto'``, we choose according to the following rules: - If ``q`` is a polynomial: When ``n`` is small or ``k`` is small with respect to ``n``, one uses the naive algorithm. When both ``n`` and ``k`` are big, one uses the cyclotomic algorithm. - If ``q`` is in the symbolic ring, one uses the cyclotomic algorithm. - Otherwise one uses the naive algorithm, unless ``q`` is a root of unity, then one uses the cyclotomic algorithm. EXAMPLES: By default, the variable is the generator of `\ZZ[q]`:: sage: from sage.combinat.q_analogues import q_binomial sage: g = q_binomial(5,1) ; g q^4 + q^3 + q^2 + q + 1 sage: g.parent() Univariate Polynomial Ring in q over Integer Ring The `q`-binomial coefficient vanishes unless `0 \leq k \leq n`:: sage: q_binomial(4,5) 0 sage: q_binomial(5,-1) 0 Other variables can be used, given as third parameter:: sage: p = ZZ['p'].gen() sage: q_binomial(4,2,p) p^4 + p^3 + 2*p^2 + p + 1 The third parameter can also be arbitrary values:: sage: q_binomial(5,1,2) == g.subs(q=2) True sage: q_binomial(5,1,1) 5 sage: q_binomial(4,2,-1) 2 sage: q_binomial(4,2,3.14) 152.030056160000 sage: R = GF(25, 't') sage: t = R.gen(0) sage: q_binomial(6, 3, t) 2*t + 3 We can also do this for more complicated objects such as matrices or symmetric functions:: sage: q_binomial(4,2,matrix([[2,1],[-1,3]])) [ -6 84] [-84 78] sage: Sym = SymmetricFunctions(QQ) sage: s = Sym.schur() sage: q_binomial(4,1, s[2]+s[1]) s[] + s[1] + s[1, 1] + s[1, 1, 1] + 2*s[2] + 4*s[2, 1] + 3*s[2, 1, 1] + 4*s[2, 2] + 3*s[2, 2, 1] + s[2, 2, 2] + 3*s[3] + 7*s[3, 1] + 3*s[3, 1, 1] + 6*s[3, 2] + 2*s[3, 2, 1] + s[3, 3] + 4*s[4] + 6*s[4, 1] + s[4, 1, 1] + 3*s[4, 2] + 3*s[5] + 2*s[5, 1] + s[6] TESTS: One checks that the first two arguments are integers:: sage: q_binomial(1/2,1) Traceback (most recent call last): ... TypeError: no conversion of this rational to integer One checks that `n` is nonnegative:: sage: q_binomial(-4,1) Traceback (most recent call last): ... ValueError: n must be nonnegative This also works for variables in the symbolic ring:: sage: z = var('z') sage: factor(q_binomial(4,2,z)) (z^2 + z + 1)*(z^2 + 1) This also works for complex roots of unity:: sage: q_binomial(10, 4, QQbar(I)) 2 Note that the symbolic computation works (see :trac:`14982`):: sage: q_binomial(10, 4, I) 2 Check that the algorithm does not matter:: sage: q_binomial(6, 3, algorithm='naive') == q_binomial(6, 3, algorithm='cyclotomic') True One more test:: sage: q_binomial(4, 2, Zmod(6)(2), algorithm='naive') 5 Check that it works with Python integers:: sage: r = q_binomial(3r, 2r, 1r); r 3 sage: type(r) <type 'int'> Check that arbitrary polynomials work:: sage: R.<x> = ZZ[] sage: q_binomial(2, 1, x^2 - 1, algorithm="naive") x^2 sage: q_binomial(2, 1, x^2 - 1, algorithm="cyclotomic") x^2 Check that the parent is always the parent of ``q``:: sage: R.<q> = CyclotomicField(3) sage: for algo in ["naive", "cyclotomic"]: ....: for n in range(4): ....: for k in range(4): ....: a = q_binomial(n, k, q, algorithm=algo) ....: assert a.parent() is R :: sage: q_binomial(2, 1, x^2 - 1, algorithm="quantum") Traceback (most recent call last): ... ValueError: unknown algorithm 'quantum' REFERENCES: .. [CH2006] William Y.C. Chen and Qing-Hu Hou, *Factors of the Gaussian coefficients*, Discrete Mathematics 306 (2006), 1446-1449. :doi:`10.1016/j.disc.2006.03.031` AUTHORS: - Frédéric Chapoton, David Joyner and William Stein """ # sanity checks n = ZZ(n) k = ZZ(k) if n < 0: raise ValueError('n must be nonnegative') k = min(n - k, k) # Pick the smallest k # polynomiality test if q is None: from sage.rings.polynomial.polynomial_ring import polygen q = polygen(ZZ, name='q') is_polynomial = True else: from sage.rings.polynomial.polynomial_element import Polynomial is_polynomial = isinstance(q, Polynomial) # We support non-Sage Elements too, where parent(q) is really # type(q). The calls R(0) and R(1) should work in all cases to # generate the correct 0 and 1 elements. R = parent(q) zero = R(0) one = R(1) if k <= 0: return one if k == 0 else zero # heuristic choice of the fastest algorithm if algorithm == 'auto': if n <= 70 or k <= n // 4: algorithm = 'naive' elif is_polynomial: algorithm = 'cyclotomic' else: from sage.symbolic.ring import SR if R is SR: algorithm = 'cyclotomic' else: algorithm = 'naive' # the algorithms while algorithm == 'naive': denom = prod(one - q**i for i in range(1, k + 1)) if not denom: # q is a root of unity, use the cyclotomic algorithm algorithm = 'cyclotomic' break else: num = prod(one - q**i for i in range(n - k + 1, n + 1)) try: try: return num // denom except TypeError: return num / denom except (TypeError, ZeroDivisionError): # use substitution instead return q_binomial(n, k)(q) if algorithm == 'cyclotomic': from sage.rings.polynomial.cyclotomic import cyclotomic_value return prod( cyclotomic_value(d, q) for d in range(2, n + 1) if (n // d) != (k // d) + ((n - k) // d)) else: raise ValueError("unknown algorithm {!r}".format(algorithm))
def contains_coeff_ring(self): r""" Return whether ``self`` contains its coefficient ring. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: MF = ModularForms(k=0, ep=1, n=8) sage: subspace = MF.subspace([1]) sage: subspace.contains_coeff_ring() True sage: subspace = MF.subspace([]) sage: subspace.contains_coeff_ring() False sage: MF = ModularForms(k=0, ep=-1, n=8) sage: subspace = MF.subspace([]) sage: subspace.contains_coeff_ring() False """ return (super(SubSpaceForms, self).contains_coeff_ring() and self.dimension()==ZZ(1))
def AdditiveAbelianGroup(invs, remember_generators=True): r""" Construct a finitely-generated additive abelian group. INPUT: - ``invs`` (list of integers): the invariants. These should all be greater than or equal to zero. - ``remember_generators`` (boolean): whether or not to fix a set of generators (corresponding to the given invariants, which need not be in Smith form). OUTPUT: The abelian group `\bigoplus_i \ZZ / n_i \ZZ`, where `n_i` are the invariants. EXAMPLES:: sage: AdditiveAbelianGroup([0, 2, 4]) Additive abelian group isomorphic to Z + Z/2 + Z/4 An example of the ``remember_generators`` switch:: sage: G = AdditiveAbelianGroup([0, 2, 3]); G Additive abelian group isomorphic to Z + Z/2 + Z/3 sage: G.gens() ((1, 0, 0), (0, 1, 0), (0, 0, 1)) sage: H = AdditiveAbelianGroup([0, 2, 3], remember_generators = False); H Additive abelian group isomorphic to Z/6 + Z sage: H.gens() ((0, 1, 2), (1, 0, 0)) There are several ways to create elements of an additive abelian group. Realize that there are two sets of generators: the "obvious" ones composed of zeros and ones, one for each invariant given to construct the group, the other being a set of minimal generators. Which set is the default varies with the use of the ``remember_generators`` switch. First with "obvious" generators. Note that a raw list will use the minimal generators and a vector (a module element) will use the generators that pair up naturally with the invariants. We create the same element repeatedly. :: sage: H=AdditiveAbelianGroup([3,2,0], remember_generators=True) sage: H.gens() ((1, 0, 0), (0, 1, 0), (0, 0, 1)) sage: [H.0, H.1, H.2] [(1, 0, 0), (0, 1, 0), (0, 0, 1)] sage: p=H.0+H.1+6*H.2; p (1, 1, 6) sage: H.smith_form_gens() ((2, 1, 0), (0, 0, 1)) sage: q=H.linear_combination_of_smith_form_gens([5,6]); q (1, 1, 6) sage: p==q True sage: r=H(vector([1,1,6])); r (1, 1, 6) sage: p==r True sage: s=H(p) sage: p==s True Again, but now where the generators are the minimal set. Coercing a list or a vector works as before, but the default generators are different. :: sage: G=AdditiveAbelianGroup([3,2,0], remember_generators=False) sage: G.gens() ((2, 1, 0), (0, 0, 1)) sage: [G.0, G.1] [(2, 1, 0), (0, 0, 1)] sage: p=5*G.0+6*G.1; p (1, 1, 6) sage: H.smith_form_gens() ((2, 1, 0), (0, 0, 1)) sage: q=G.linear_combination_of_smith_form_gens([5,6]); q (1, 1, 6) sage: p==q True sage: r=G(vector([1,1,6])); r (1, 1, 6) sage: p==r True sage: s=H(p) sage: p==s True """ invs = [ZZ(x) for x in invs] if not all(x >= 0 for x in invs): raise ValueError("Invariants must be nonnegative") A, B = cover_and_relations_from_invariants(invs) if remember_generators: G = AdditiveAbelianGroup_fixed_gens(A, B, A.gens()) else: G = AdditiveAbelianGroup_class(A, B) return G
def dvalue(self): r""" Return a symbolic expression (or an exact value in case n=3, 4, 6) for the transfinite diameter (or capacity) of ``self``. I.e. the first nontrivial Fourier coefficient of the Hauptmodul for the Hecke triangle group in case it is normalized to ``J_inv(i)=1``. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: HeckeTriangleGroup(3).dvalue() 1/1728 sage: HeckeTriangleGroup(4).dvalue() 1/256 sage: HeckeTriangleGroup(5).dvalue() e^(2*euler_gamma - 4*pi/(sqrt(5) + 1) + psi(17/20) + psi(13/20)) sage: HeckeTriangleGroup(6).dvalue() 1/108 sage: HeckeTriangleGroup(10).dvalue() e^(2*euler_gamma - 4*pi/sqrt(2*sqrt(5) + 10) + psi(4/5) + psi(7/10)) sage: HeckeTriangleGroup(infinity).dvalue() 1/64 """ n = self._n if (n == 3): return ZZ(1) / ZZ(2**6 * 3**3) elif (n == 4): return ZZ(1) / ZZ(2**8) elif (n == 6): return ZZ(1) / ZZ(2**2 * 3**3) elif (n == infinity): return ZZ(1) / ZZ(2**6) else: return exp(-ZZ(2) * psi1(ZZ(1)) + psi1(ZZ(1) - self.alpha()) + psi1(ZZ(1) - self.beta()) - pi * sec(pi / self._n))
def get_FD(self, z): r""" Return a tuple (A,w) which determines how to map ``z`` to the usual (strict) fundamental domain of ``self``. INPUT: - ``z`` -- a complex number or an element of AlgebraicField(). OUTPUT: A tuple ``(A, w)``. - ``A`` -- a matrix in ``self`` such that ``A.acton(w)==z`` (if ``z`` is exact at least). - ``w`` -- a complex number or an element of AlgebraicField() (depending on the type ``z``) which lies inside the (strict) fundamental domain of ``self`` (``self.in_FD(w)==True``) and which is equivalent to ``z`` (by the above property). EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(8) sage: z = AlgebraicField()(1+i/2) sage: (A, w) = G.get_FD(z) sage: A [-lam 1] [ -1 0] sage: A.acton(w) == z True sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: z = (134.12 + 0.22*i).n() sage: (A, w) = G.get_FD(z) sage: A [-73*lam^3 + 74*lam 73*lam^2 - 1] [ -lam^2 + 1 lam] sage: w 0.769070776942... + 0.779859114103...*I sage: z 134.120000000... + 0.220000000000...*I sage: A.acton(w) 134.1200000... + 0.2200000000...*I """ ID = self.I() T = self.T() S = self.S() TI = self.T(-1) A = ID w = z while (abs(w) < ZZ(1) or abs(w.real()) > self.lam() / ZZ(2)): if (abs(w) < ZZ(1)): w = self.S().acton(w) A = S * A while (w.real() >= self.lam() / ZZ(2)): w = TI.acton(w) A = TI * A while (w.real() < -self.lam() / ZZ(2)): w = T.acton(w) A = T * A if (w.real() == self.lam() / ZZ(2)): w = TI.acton(w) A = TI * A if (abs(w) == ZZ(1) and w.real() > ZZ(0)): w = S.acton(w) A = S * A AI = A.inverse() return (AI, A.acton(z))
def contradicts(self, soln): """ Returns ``True`` if this assumption is violated by the given variable assignment(s). INPUT: - ``soln`` - Either a dictionary with variables as keys or a symbolic relation with a variable on the left hand side. EXAMPLES:: sage: from sage.symbolic.assumptions import GenericDeclaration sage: GenericDeclaration(x, 'integer').contradicts(x==4) False sage: GenericDeclaration(x, 'integer').contradicts(x==4.0) False sage: GenericDeclaration(x, 'integer').contradicts(x==4.5) True sage: GenericDeclaration(x, 'integer').contradicts(x==sqrt(17)) True sage: GenericDeclaration(x, 'noninteger').contradicts(x==sqrt(17)) False sage: GenericDeclaration(x, 'noninteger').contradicts(x==17) True sage: GenericDeclaration(x, 'even').contradicts(x==3) True sage: GenericDeclaration(x, 'complex').contradicts(x==3) False sage: GenericDeclaration(x, 'imaginary').contradicts(x==3) True sage: GenericDeclaration(x, 'imaginary').contradicts(x==I) False sage: var('y,z') (y, z) sage: GenericDeclaration(x, 'imaginary').contradicts(x==y+z) False sage: GenericDeclaration(x, 'rational').contradicts(y==pi) False sage: GenericDeclaration(x, 'rational').contradicts(x==pi) True sage: GenericDeclaration(x, 'irrational').contradicts(x!=pi) False sage: GenericDeclaration(x, 'rational').contradicts({x: pi, y: pi}) True sage: GenericDeclaration(x, 'rational').contradicts({z: pi, y: pi}) False """ if isinstance(soln, dict): value = soln.get(self._var) if value is None: return False elif soln.lhs() == self._var: value = soln.rhs() else: return False try: CC(value) except: return False if self._assumption == 'integer': return value not in ZZ elif self._assumption == 'noninteger': return value in ZZ elif self._assumption == 'even': return value not in ZZ or ZZ(value) % 2 != 0 elif self._assumption == 'odd': return value not in ZZ or ZZ(value) % 2 != 1 elif self._assumption == 'rational': return value not in QQ elif self._assumption == 'irrational': return value in QQ elif self._assumption == 'real': return value not in RR elif self._assumption == 'imaginary': return value not in CC or CC(value).real() != 0 elif self._assumption == 'complex': return value not in CC
def BinaryQF_reduced_representatives(D, primitive_only=False): r""" Returns a list of inequivalent reduced representatives for the equivalence classes of positive definite binary forms of discriminant D. INPUT: - `D` -- (integer) A negative discriminant. - ``primitive_only`` -- (bool, default False) flag controlling whether only primitive forms are included. OUTPUT: (list) A lexicographically-ordered list of inequivalent reduced representatives for the equivalence classes of positive definite binary forms of discriminant `D`. If ``primitive_only`` is ``True`` then imprimitive forms (which only exist when `D` is not fundamental) are omitted; otherwise they are included. EXAMPLES:: sage: BinaryQF_reduced_representatives(-4) [x^2 + y^2] sage: BinaryQF_reduced_representatives(-163) [x^2 + x*y + 41*y^2] sage: BinaryQF_reduced_representatives(-12) [x^2 + 3*y^2, 2*x^2 + 2*x*y + 2*y^2] sage: BinaryQF_reduced_representatives(-16) [x^2 + 4*y^2, 2*x^2 + 2*y^2] sage: BinaryQF_reduced_representatives(-63) [x^2 + x*y + 16*y^2, 2*x^2 - x*y + 8*y^2, 2*x^2 + x*y + 8*y^2, 3*x^2 + 3*x*y + 6*y^2, 4*x^2 + x*y + 4*y^2] The number of inequivalent reduced binary forms with a fixed negative fundamental discriminant D is the class number of the quadratic field `Q(\sqrt{D})`:: sage: len(BinaryQF_reduced_representatives(-13*4)) 2 sage: QuadraticField(-13*4, 'a').class_number() 2 sage: p=next_prime(2^20); p 1048583 sage: len(BinaryQF_reduced_representatives(-p)) 689 sage: QuadraticField(-p, 'a').class_number() 689 sage: BinaryQF_reduced_representatives(-23*9) [x^2 + x*y + 52*y^2, 2*x^2 - x*y + 26*y^2, 2*x^2 + x*y + 26*y^2, 3*x^2 + 3*x*y + 18*y^2, 4*x^2 - x*y + 13*y^2, 4*x^2 + x*y + 13*y^2, 6*x^2 - 3*x*y + 9*y^2, 6*x^2 + 3*x*y + 9*y^2, 8*x^2 + 7*x*y + 8*y^2] sage: BinaryQF_reduced_representatives(-23*9, primitive_only=True) [x^2 + x*y + 52*y^2, 2*x^2 - x*y + 26*y^2, 2*x^2 + x*y + 26*y^2, 4*x^2 - x*y + 13*y^2, 4*x^2 + x*y + 13*y^2, 8*x^2 + 7*x*y + 8*y^2] TESTS:: sage: BinaryQF_reduced_representatives(5) Traceback (most recent call last): ... ValueError: discriminant must be negative and congruent to 0 or 1 modulo 4 """ D = ZZ(D) if not ( D < 0 and (D % 4 in [0,1])): raise ValueError("discriminant must be negative and congruent to 0 or 1 modulo 4") # For a fundamental discriminant all forms are primitive so we need not check: if primitive_only: primitive_only = not is_fundamental_discriminant(D) form_list = [] from sage.arith.srange import xsrange # Only iterate over positive a and over b of the same # parity as D such that 4a^2 + D <= b^2 <= a^2 for a in xsrange(1,1+((-D)//3).isqrt()): a4 = 4*a s = D + a*a4 w = 1+(s-1).isqrt() if s > 0 else 0 if w%2 != D%2: w += 1 for b in xsrange(w,a+1,2): t = b*b-D if t % a4 == 0: c = t // a4 if (not primitive_only) or gcd([a,b,c])==1: if b>0 and a>b and c>a: form_list.append(BinaryQF([a,-b,c])) form_list.append(BinaryQF([a,b,c])) form_list.sort() return form_list
def h(c): den = c.denominator() num = den * c l = list(num) l.append(den) return max(ZZ(a).nbits() for a in l)
def _sage_(self): """ Convert self to a Sage object. EXAMPLES:: sage: a = axiom(1/2); a #optional - axiom 1 - 2 sage: a.sage() #optional - axiom 1/2 sage: _.parent() #optional - axiom Rational Field sage: gp(axiom(1/2)) #optional - axiom 1/2 sage: fricas(1/2).sage() #optional - fricas 1/2 DoubleFloat's in Axiom are converted to be in RDF in Sage. :: sage: axiom(2.0).as_type('DoubleFloat').sage() #optional - axiom 2.0 sage: _.parent() #optional - axiom Real Double Field sage: axiom(2.1234)._sage_() #optional - axiom 2.12340000000000 sage: _.parent() #optional - axiom Real Field with 53 bits of precision sage: a = RealField(100)(pi) sage: axiom(a)._sage_() #optional - axiom 3.1415926535897932384626433833 sage: _.parent() #optional - axiom Real Field with 100 bits of precision sage: axiom(a)._sage_() == a #optional - axiom True sage: axiom(2.0)._sage_() #optional - axiom 2.00000000000000 sage: _.parent() #optional - axiom Real Field with 53 bits of precision We can also convert Axiom's polynomials to Sage polynomials. sage: a = axiom(x^2 + 1) #optional - axiom sage: a.type() #optional - axiom Polynomial Integer sage: a.sage() #optional - axiom x^2 + 1 sage: _.parent() #optional - axiom Univariate Polynomial Ring in x over Integer Ring sage: axiom('x^2 + y^2 + 1/2').sage() #optional - axiom y^2 + x^2 + 1/2 sage: _.parent() #optional - axiom Multivariate Polynomial Ring in y, x over Rational Field """ P = self._check_valid() type = str(self.type()) if type in ["Type", "Domain"]: return self._sage_domain() if type == "Float": from sage.rings.all import RealField, ZZ prec = max(self.mantissa().length()._sage_(), 53) R = RealField(prec) x,e,b = self.unparsed_input_form().lstrip('float(').rstrip(')').split(',') return R(ZZ(x)*ZZ(b)**ZZ(e)) elif type == "DoubleFloat": from sage.rings.all import RDF return RDF(repr(self)) elif type.startswith('Polynomial'): from sage.rings.all import PolynomialRing base_ring = P(type.lstrip('Polynomial '))._sage_domain() vars = str(self.variables())[1:-1] R = PolynomialRing(base_ring, vars) return R(self.unparsed_input_form()) #If all else fails, try using the unparsed input form try: import sage.misc.sage_eval return sage.misc.sage_eval.sage_eval(self.unparsed_input_form()) except StandardError: raise NotImplementedError
def interval_series_sum_wrapper(dop, inis, pt, tgt_error, bwrec, stop, fail_fast, effort, stride, ctx=dctx): real = pt.is_real_or_symbolic() and all(ini.is_real(dop) for ini in inis) if pt.is_numeric and cyPartialSum() is not PartialSum: ivs = ComplexBallField elif real: ivs = RealBallField else: ivs = ComplexBallField input_accuracy = max(0, min(chain([pt.accuracy()], (ini.accuracy() for ini in inis)))) logger.log(logging.INFO - 1, "target error = %s", tgt_error) if stride is None: stride = min(max(50, 2*bwrec.order), max(2, input_accuracy)) ordinary = dop.leading_coefficient()[0] != 0 bit_prec0 = utilities.prec_from_eps(tgt_error.eps) old_bit_prec = 8 + bit_prec0*(1 + ZZ(bwrec.order - 2).nbits()) if ctx.squash_intervals and ordinary: nterms, lg_mag = dop.est_terms(pt, bit_prec0) nterms = (bwrec.order*dop.order() + nterms)*1.2 # let's be pragmatic nterms = ZZ((nterms//stride + 1)*stride) bit_prec0 += ZZ(dop._naive_height()).nbits() + lg_mag + nterms.nbits() n0_squash, g = guard_bits(dop, stop.maj, pt, bwrec.order, nterms) # adding twice the computed number of guard bits seems to work better # in practice, but I don't really understand why bit_prec = bit_prec0 + 2*g logger.info("initial working precision = %s + %s = %s (naive = %s), " "squashing intervals for n >= %s", bit_prec0, 2*g, bit_prec, old_bit_prec, n0_squash) if fail_fast and bit_prec > 4*bit_prec0 and effort <= 1: raise accuracy.PrecisionError else: bit_prec = old_bit_prec n0_squash = sys.maxsize logger.info("initial working precision = %s bits", bit_prec) max_prec = bit_prec + 2*input_accuracy err=None for attempt in count(1): Intervals = ivs(bit_prec) ini_are_accurate = 2*input_accuracy > bit_prec # Strictly decrease eps every time to avoid situations where doit # would be happy with the result and stop at the same point despite # the higher bit_prec. Since attempt starts at 1, we have a bit of # room for round-off errors. stop.reset(tgt_error.eps >> (4*attempt), stop.fast_fail and ini_are_accurate) if _use_inexact_recurrence(bwrec, bit_prec): bwrec1 = bwrec.change_base(Intervals) else: bwrec1 = bwrec try: sols = series_sum_regular(Intervals, dop, bwrec1, inis, pt, stop, stride, n0_squash, real) except accuracy.PrecisionError: if attempt > effort: raise else: logger.debug("bit_prec = %s, err = %s (tgt = %s)", bit_prec, max(sol.total_error for sol in sols), tgt_error) if all(tgt_error.reached( sol.total_error, abs(sol.value[0]) if pt.is_numeric else None) for sol in sols): return sols # if interval squashing didn't give accurate result, switch back to the # classical method n0_squash = sys.maxsize bit_prec *= 2 if attempt <= effort and bit_prec < max_prec: logger.info("lost too much precision, restarting with %d bits", bit_prec) continue if fail_fast: raise accuracy.PrecisionError else: logger.info("lost too much precision, giving up") return sols
def __init__(self, coxeter_matrix, base_ring, index_set): """ Initialize ``self``. EXAMPLES:: sage: W = CoxeterGroup([[1,3,2],[3,1,3],[2,3,1]]) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,4],[2,4,1]], base_ring=QQbar) sage: TestSuite(W).run() # long time sage: W = CoxeterGroup([[1,3,2],[3,1,6],[2,6,1]]) sage: TestSuite(W).run(max_runs=30) # long time sage: W = CoxeterGroup([[1,3,2],[3,1,-1],[2,-1,1]]) sage: TestSuite(W).run(max_runs=30) # long time We check that :trac:`16630` is fixed:: sage: CoxeterGroup(['D',4], base_ring=QQ).category() Category of finite irreducible coxeter groups sage: CoxeterGroup(['H',4], base_ring=QQbar).category() Category of finite irreducible coxeter groups sage: F = CoxeterGroups().Finite() sage: all(CoxeterGroup([letter,i]) in F ....: for i in range(2,5) for letter in ['A','B','D']) True sage: all(CoxeterGroup(['E',i]) in F for i in range(6,9)) True sage: CoxeterGroup(['F',4]).category() Category of finite irreducible coxeter groups sage: CoxeterGroup(['G',2]).category() Category of finite irreducible coxeter groups sage: all(CoxeterGroup(['H',i]) in F for i in range(3,5)) True sage: all(CoxeterGroup(['I',i]) in F for i in range(2,5)) True """ self._matrix = coxeter_matrix n = coxeter_matrix.rank() # Compute the matrix with entries `2 \cos( \pi / m_{ij} )`. MS = MatrixSpace(base_ring, n, sparse=True) one = MS.one() # FIXME: Hack because there is no ZZ \cup \{ \infty \}: -1 represents \infty E = UniversalCyclotomicField().gen if base_ring is UniversalCyclotomicField(): def val(x): if x == -1: return 2 else: return E(2 * x) + ~E(2 * x) elif is_QuadraticField(base_ring): def val(x): if x == -1: return 2 else: return base_ring( (E(2 * x) + ~E(2 * x)).to_cyclotomic_field()) else: from sage.functions.trig import cos from sage.symbolic.constants import pi def val(x): if x == -1: return 2 else: return base_ring(2 * cos(pi / x)) gens = [ one + MS([ SparseEntry(i, j, val(coxeter_matrix[index_set[i], index_set[j]])) for j in range(n) ]) for i in range(n) ] # Make the generators dense matrices for consistency and speed gens = [g.dense_matrix() for g in gens] category = CoxeterGroups() # Now we shall see if the group is finite, and, if so, refine # the category to ``category.Finite()``. Otherwise the group is # infinite and we refine the category to ``category.Infinite()``. if self._matrix.is_finite(): category = category.Finite() else: category = category.Infinite() if all(self._matrix._matrix[i, j] == 2 for i in range(n) for j in range(i)): category = category.Commutative() if self._matrix.is_irreducible(): category = category.Irreducible() self._index_set_inverse = { i: ii for ii, i in enumerate(self._matrix.index_set()) } FinitelyGeneratedMatrixGroup_generic.__init__(self, ZZ(n), base_ring, gens, category=category)
def _conjugacy_representatives(self, max_block_length=ZZ(0), D=None): r""" Store conjugacy representatives up to block length ``max_block_length`` (a non-negative integer, default: 0) in the internal dictionary. Previously calculated data is reused. This is a helper function for e.g. :meth:`class_number`. The set of all (hyperbolic) conjugacy types of block length ``t`` is stored in ``self._conj_block[t]``. The set of all primitive representatives (so far) with discriminant ``D`` is stored in ``self._conj_prim[D]``. The set of all non-primitive representatives (so far) with discriminant ``D`` is stored in ``self._conj_nonprim[D]``. The case of non-positive discriminants is done manually. INPUT: - ``max_block_length`` -- A non-negative integer (default: ``0``), the maximal block length. - ``D`` -- An element/discriminant of the base ring or more generally an upper bound for the involved discriminants. If ``D != None`` then an upper bound for ``max_block_length`` is deduced from ``D`` (default: ``None``). EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=5) sage: G.element_repr_method("conj") sage: G._conjugacy_representatives(2) sage: list(G._conj_block[2]) [((4, 1), (3, 1)), ((2, 2),), ((3, 2),), ((3, 1), (1, 1)), ((4, 1), (1, 1)), ((4, 1), (2, 1)), ((3, 1), (2, 1)), ((2, 1), (1, 1))] sage: [key for key in sorted(G._conj_prim)] [-4, lam - 3, 0, 4*lam, 7*lam + 6, 9*lam + 5, 15*lam + 6, 33*lam + 21] sage: for key in sorted(G._conj_prim): ....: print(G._conj_prim[key]) [[S], [S]] [[U], [U]] [[V(4)]] [[V(3)], [V(2)]] [[V(1)*V(4)]] [[V(3)*V(4)], [V(1)*V(2)]] [[V(1)*V(3)], [V(2)*V(4)]] [[V(2)*V(3)]] sage: [key for key in sorted(G._conj_nonprim)] [-lam - 2, lam - 3, 32*lam + 16] sage: for key in sorted(G._conj_nonprim): ....: print(G._conj_nonprim[key]) [[U^(-2)], [U^2], [U^(-2)], [U^2]] [[U^(-1)], [U^(-1)]] [[V(2)^2], [V(3)^2]] sage: G.element_repr_method("default") """ from sage.combinat.partition import OrderedPartitions from sage.combinat.combinat import tuples from sage.arith.all import divisors if not D is None: max_block_length = max(coerce_AA(0), coerce_AA((D + 4) / (self.lam()**2))).sqrt().floor() else: try: max_block_length = ZZ(max_block_length) if max_block_length < 0: raise TypeError except TypeError: raise ValueError( "max_block_length must be a non-negative integer!") if not hasattr(self, "_max_block_length"): self._max_block_length = ZZ(0) self._conj_block = {} self._conj_nonprim = {} self._conj_prim = {} # It is not clear how to define the class number for D=0: # Conjugacy classes are V(n-1)^(+-k) for arbitrary k # and the trivial class (what about self_conj_block[0]?). # # One way is to define it using the fixed points and in # that case V(n-1) would be a good representative. # The non-primitive case is unclear however... # # We set it here to ensure that 0 is enlisted as a discriminant... # self._conj_prim[ZZ(0)] = [] self._conj_prim[ZZ(0)].append(self.V(self.n() - 1)) self._elliptic_conj_reps() if max_block_length <= self._max_block_length: return def is_cycle(seq): length = len(seq) for n in divisors(length): if n < length and is_cycle_of_length(seq, n): return True return False def is_cycle_of_length(seq, n): for i in range(n, len(seq)): if seq[i] != seq[i % n]: return False return True j_list = range(1, self.n()) for t in range(self._max_block_length + 1, max_block_length + 1): t_ZZ = ZZ(t) if t_ZZ not in self._conj_block: self._conj_block[t_ZZ] = set() partitions = OrderedPartitions(t).list() for par in partitions: len_par = len(par) exp_list = tuples(j_list, len_par) for ex in exp_list: keep = True if len_par > 1: for k in range(-1, len_par - 1): if ex[k] == ex[k + 1]: keep = False break # We don't want powers of V(1) elif ex[0] == 1: keep = False # But: Do we maybe want powers of V(n-1)?? # For now we exclude the parabolic cases... elif ex[0] == self.n() - 1: keep = False if keep: conj_type = cyclic_representative( tuple((ZZ(ex[k]), ZZ(par[k])) for k in range(len_par))) self._conj_block[t_ZZ].add(conj_type) for el in self._conj_block[t_ZZ]: group_el = prod( [self.V(el[k][0])**el[k][1] for k in range(len(el))]) #if el != group_el.conjugacy_type(): # raise AssertionError("This shouldn't happen!") D = group_el.discriminant() if coerce_AA(D) < 0: raise AssertionError("This shouldn't happen!") if coerce_AA(D) == 0: raise AssertionError("This shouldn't happen!") #continue # The primitive cases #if group_el.is_primitive(): if not ((len(el) == 1 and el[0][1] > 1) or is_cycle(el)): if D not in self._conj_prim: self._conj_prim[D] = [] self._conj_prim[D].append(group_el) # The remaining cases else: if D not in self._conj_nonprim: self._conj_nonprim[D] = [] self._conj_nonprim[D].append(group_el) self._max_block_length = max_block_length
def normal_cone(self): r""" Return the (closure of the) normal cone of the triangulation. Recall that a regular triangulation is one that equals the "crease lines" of a convex piecewise-linear function. This support function is not unique, for example, you can scale it by a positive constant. The set of all piecewise-linear functions with fixed creases forms an open cone. This cone can be interpreted as the cone of normal vectors at a point of the secondary polytope, which is why we call it normal cone. See [GKZ]_ Section 7.1 for details. OUTPUT: The closure of the normal cone. The `i`-th entry equals the value of the piecewise-linear function at the `i`-th point of the configuration. For an irregular triangulation, the normal cone is empty. In this case, a single point (the origin) is returned. EXAMPLES:: sage: triangulation = polytopes.n_cube(2).triangulate(engine='internal') sage: triangulation (<0,1,3>, <0,2,3>) sage: N = triangulation.normal_cone(); N 4-d cone in 4-d lattice sage: N.rays() (-1, 0, 0, 0), ( 1, 0, 1, 0), (-1, 0, -1, 0), ( 1, 0, 0, -1), (-1, 0, 0, 1), ( 1, 1, 0, 0), (-1, -1, 0, 0) in Ambient free module of rank 4 over the principal ideal domain Integer Ring sage: N.dual().rays() (-1, 1, 1, -1) in Ambient free module of rank 4 over the principal ideal domain Integer Ring TESTS:: sage: polytopes.n_simplex(2).triangulate().normal_cone() 3-d cone in 3-d lattice sage: _.dual().is_trivial() True """ if not self.point_configuration().base_ring().is_subring(QQ): raise NotImplementedError( 'Only base rings ZZ and QQ are supported') from sage.libs.ppl import Variable, Constraint, Constraint_System, Linear_Expression, C_Polyhedron from sage.matrix.constructor import matrix from sage.misc.misc import uniq from sage.rings.arith import lcm pc = self.point_configuration() cs = Constraint_System() for facet in self.interior_facets(): s0, s1 = self._boundary_simplex_dictionary()[facet] p = set(s0).difference(facet).pop() q = set(s1).difference(facet).pop() origin = pc.point(p).reduced_affine_vector() base_indices = [i for i in s0 if i != p] base = matrix([ pc.point(i).reduced_affine_vector() - origin for i in base_indices ]) sol = base.solve_left(pc.point(q).reduced_affine_vector() - origin) relation = [0] * pc.n_points() relation[p] = sum(sol) - 1 relation[q] = 1 for i, base_i in enumerate(base_indices): relation[base_i] = -sol[i] rel_denom = lcm([QQ(r).denominator() for r in relation]) relation = [ZZ(r * rel_denom) for r in relation] ex = Linear_Expression(relation, 0) cs.insert(ex >= 0) from sage.modules.free_module import FreeModule ambient = FreeModule(ZZ, self.point_configuration().n_points()) if cs.empty(): cone = C_Polyhedron(ambient.dimension(), 'universe') else: cone = C_Polyhedron(cs) from sage.geometry.cone import _Cone_from_PPL return _Cone_from_PPL(cone, lattice=ambient)
def sturm_bound(self, weight=2): r""" Returns the Sturm bound for modular forms of the given weight and level this subgroup. INPUT: - ``weight`` - an integer `\geq 2` (default: 2) EXAMPLES:: sage: Gamma0(11).sturm_bound(2) 2 sage: Gamma0(389).sturm_bound(2) 65 sage: Gamma0(1).sturm_bound(12) 1 sage: Gamma0(100).sturm_bound(2) 30 sage: Gamma0(1).sturm_bound(36) 3 sage: Gamma0(11).sturm_bound() 2 sage: Gamma0(13).sturm_bound() 3 sage: Gamma0(16).sturm_bound() 4 sage: GammaH(16,[13]).sturm_bound() 8 sage: GammaH(16,[15]).sturm_bound() 16 sage: Gamma1(16).sturm_bound() 32 sage: Gamma1(13).sturm_bound() 28 sage: Gamma1(13).sturm_bound(5) 70 FURTHER DETAILS: This function returns a positive integer `n` such that the Hecke operators `T_1,\ldots, T_n` acting on *cusp forms* generate the Hecke algebra as a `\ZZ`-module when the character is trivial or quadratic. Otherwise, `T_1,\ldots,T_n` generate the Hecke algebra at least as a `\ZZ[\varepsilon]`-module, where `\ZZ[\varepsilon]` is the ring generated by the values of the Dirichlet character `\varepsilon`. Alternatively, this is a bound such that if two cusp forms associated to this space of modular symbols are congruent modulo `(\lambda, q^n)`, then they are congruent modulo `\lambda`. REFERENCES: - See the Agashe-Stein appendix to Lario and Schoof, *Some computations with Hecke rings and deformation rings*, Experimental Math., 11 (2002), no. 2, 303-311. - This result originated in the paper Sturm, *On the congruence of modular forms*, Springer LNM 1240, 275-280, 1987. REMARK: Kevin Buzzard pointed out to me (William Stein) in Fall 2002 that the above bound is fine for `\Gamma_1(N)` with character, as one sees by taking a power of `f`. More precisely, if `f \cong 0 \pmod{p}` for first `s` coefficients, then `f^r \cong 0 \pmod{p}` for first `sr` coefficients. Since the weight of `f^r` is `r\cdot k(f)`, it follows that if `s \geq b`, where `b` is the Sturm bound for `\Gamma_0(N)` at weight `k(f)`, then `f^r` has valuation large enough to be forced to be `0` at `r*k(f)` by Sturm bound (which is valid if we choose `r` correctly). Thus `f \cong 0 \pmod{p}`. Conclusion: For `\Gamma_1(N)` with fixed character, the Sturm bound is *exactly* the same as for `\Gamma_0(N)`. A key point is that we are finding `\ZZ[\varepsilon]` generators for the Hecke algebra here, not `\ZZ`-generators. So if one wants generators for the Hecke algebra over `\ZZ`, this bound must be suitably modified (and I'm not sure what the modification is). AUTHORS: - William Stein """ return ZZ((self.index() * weight / ZZ(12)).ceil())
def validate_mwrank_input(s): r""" Returns a string suitable for mwrank input, or raises an error. INPUT: - `s` -- one of the following: - a list or tuple of 5 integers [a1,a2,a3,a4,a6] or (a1,a2,a3,a4,a6) - a string of the form '[a1,a2,a3,a4,a6]' or 'a1 a2 a3 a4 a6' where a1, a2, a3, a4, a6 are integers OUTPUT: For valid input, a string of the form '[a1,a2,a3,a4,a6]'. For invalid input a ValueError is raised. EXAMPLES: A list or tuple of 5 integers:: sage: from sage.interfaces.mwrank import validate_mwrank_input sage: validate_mwrank_input([1,2,3,4,5]) '[1, 2, 3, 4, 5]' sage: validate_mwrank_input((-1,2,-3,4,-55)) '[-1, 2, -3, 4, -55]' sage: validate_mwrank_input([1,2,3,4]) Traceback (most recent call last): ... ValueError: [1, 2, 3, 4] is not valid input to mwrank (should have 5 entries) sage: validate_mwrank_input([1,2,3,4,i]) Traceback (most recent call last): ... ValueError: [1, 2, 3, 4, I] is not valid input to mwrank (entries should be integers) A string of the form '[a1,a2,a3,a4,a6]' with any whitespace and integers ai:: sage: validate_mwrank_input('0 -1 1 -7 6') '[0,-1,1,-7,6]' sage: validate_mwrank_input("[0,-1,1,0,0]\n") '[0,-1,1,0,0]' sage: validate_mwrank_input('0\t -1\t 1\t 0\t 0\n') '[0,-1,1,0,0]' sage: validate_mwrank_input('0 -1 1 -7 ') Traceback (most recent call last): ... ValueError: 0 -1 1 -7 is not valid input to mwrank """ if isinstance(s,(list,tuple)): from sage.rings.all import ZZ if len(s)!=5: raise ValueError("%s is not valid input to mwrank (should have 5 entries)" % s) try: ai = [ZZ(a) for a in s] return str(ai) except (TypeError,ValueError): raise ValueError("%s is not valid input to mwrank (entries should be integers)" % s) if isinstance(s,str): if AINVS_PLAIN_RE.match(s): ai = s.split() return "["+",".join(ai)+"]" ss = s.replace(' ','').replace('\n','').replace('\t','') if AINVS_LIST_RE.match(ss): return ss raise ValueError("%s is not valid input to mwrank" % s)
def _find_generators(self, maxweight, start_gens, start_weight): r""" For internal use. This function is called by :meth:`generators` and :meth:`gen_forms`: it returns a list of triples `(k, f, F)` where `F` is a modular form of weight `k` and `f` is its `q`-expansion coerced into the base ring of self. INPUT: - maxweight: maximum weight to try - start_weight: minimum weight to try - start_gens: a sequence of tuples of the form `(k, f, F)`, where `F` is a modular form of weight `k` and `f` is its `q`-expansion coerced into ``self.base_ring()`. Either (but not both) of `f` and `F` may be None. OUTPUT: a list of tuples, formatted as with ``start_gens``. EXAMPLES:: sage: R = ModularFormsRing(Gamma1(4)) sage: R._find_generators(8, (), 2) [(2, 1 + 24*q^2 + 24*q^4 + 96*q^6 + 24*q^8 + O(q^9), 1 + 24*q^2 + 24*q^4 + O(q^6)), (2, q + 4*q^3 + 6*q^5 + 8*q^7 + O(q^9), q + 4*q^3 + 6*q^5 + O(q^6)), (3, 1 + 12*q^2 + 64*q^3 + 60*q^4 + 160*q^6 + 384*q^7 + 252*q^8 + O(q^9), 1 + 12*q^2 + 64*q^3 + 60*q^4 + O(q^6)), (3, q + 4*q^2 + 8*q^3 + 16*q^4 + 26*q^5 + 32*q^6 + 48*q^7 + 64*q^8 + O(q^9), q + 4*q^2 + 8*q^3 + 16*q^4 + 26*q^5 + O(q^6))] """ default_params = (start_gens == () and start_weight == 2) if default_params and self.__cached_maxweight != -1: verbose("Already know generators up to weight %s -- using those" % self.__cached_maxweight) if self.__cached_maxweight >= maxweight: return [(k, f, F) for k, f, F in self.__cached_gens if k <= maxweight] start_gens = self.__cached_gens start_weight = self.__cached_maxweight + 1 if self.group().is_even(): increment = 2 else: increment = 1 working_prec = self.modular_forms_of_weight(maxweight).sturm_bound() # parse the list of start gens G = [] for x in start_gens: k, f, F = x if F is None and f.prec() < working_prec: raise ValueError("Need start gens to precision at least %s" % working_prec) elif f is None or f.prec() < working_prec: f = F.qexp(working_prec).change_ring(self.base_ring()) G.append((k, f, F)) k = start_weight if increment == 2 and (k % 2) == 1: k += 1 while k <= maxweight: if self.modular_forms_of_weight(k).dimension() == 0: k += increment continue verbose('Looking at k = %s' % k) M = self.modular_forms_of_weight(k) # 1. Multiply together all forms in G that give an element # of M. if G != []: F = _span_of_forms_in_weight(G, k, M.sturm_bound(), None, False) else: F = (self.base_ring()**M.sturm_bound()).zero_submodule() # 2. If the dimension of the span of the result is equal # to the dimension of M, increment k. if F.rank() == M.dimension(): if self.base_ring().is_field() or F.index_in_saturation() == 1: # TODO: Do something clever if the submodule's of the right # rank but not saturated -- avoid triggering needless # modular symbol computations. verbose('Nothing new in weight %s' % k) k += increment continue # 3. If the dimension is less, compute a basis for G, and # try adding basis elements of M into G. verbose( "Known generators span a subspace of dimension %s of space of dimension %s" % (F.dimension(), M.dimension())) if self.base_ring() == ZZ: verbose("saturation index is %s" % F.index_in_saturation()) t = verbose("Computing more modular forms at weight %s" % k) kprec = M.sturm_bound() if self.base_ring() == QQ: B = M.q_echelon_basis(working_prec) else: B = M.q_integral_basis(working_prec) t = verbose("done computing forms", t) V = F.ambient_module().submodule_with_basis( [f.padded_list(kprec) for f in B]) Q = V / F for q in Q.gens(): try: qc = V.coordinates(Q.lift(q)) except AttributeError: # work around a silly free module bug qc = V.coordinates(q.lift()) qcZZ = [ZZ(_) for _ in qc] # lift to ZZ so we can define F f = sum([B[i] * qcZZ[i] for i in range(len(B))]) F = M(f) G.append((k, f.change_ring(self.base_ring()), F)) verbose('added %s new generators' % Q.ngens(), t) k += increment if default_params: self.__cached_maxweight = maxweight self.__cached_gens = G return G
def is_gamma0_equiv(self, other, N, transformation=None): r""" Return whether self and other are equivalent modulo the action of `\Gamma_0(N)` via linear fractional transformations. INPUT: - ``other`` - Cusp - ``N`` - an integer (specifies the group Gamma_0(N)) - ``transformation`` - None (default) or either the string 'matrix' or 'corner'. If 'matrix', it also returns a matrix in Gamma_0(N) that sends self to other. The matrix is chosen such that the lower left entry is as small as possible in absolute value. If 'corner' (or True for backwards compatibility), it returns only the upper left entry of such a matrix. OUTPUT: - a boolean - True if self and other are equivalent - a matrix or an integer- returned only if transformation is 'matrix' or 'corner', respectively. EXAMPLES:: sage: x = Cusp(2,3) sage: y = Cusp(4,5) sage: x.is_gamma0_equiv(y, 2) True sage: _, ga = x.is_gamma0_equiv(y, 2, 'matrix'); ga [-1 2] [-2 3] sage: x.is_gamma0_equiv(y, 3) False sage: x.is_gamma0_equiv(y, 3, 'matrix') (False, None) sage: Cusp(1/2).is_gamma0_equiv(1/3,11,'corner') (True, 19) sage: Cusp(1,0) Infinity sage: z = Cusp(1,0) sage: x.is_gamma0_equiv(z, 3, 'matrix') ( [-1 1] True, [-3 2] ) ALGORITHM: See Proposition 2.2.3 of Cremona's book 'Algorithms for Modular Elliptic Curves', or Prop 2.27 of Stein's Ph.D. thesis. """ if transformation not in [False, True, "matrix", None, "corner"]: raise ValueError( "Value %s of the optional argument transformation is not valid." ) if not isinstance(other, Cusp): other = Cusp(other) N = ZZ(N) u1 = self.__a v1 = self.__b u2 = other.__a v2 = other.__b zero = ZZ.zero() one = ZZ.one() if transformation == "matrix": from sage.matrix.constructor import matrix if v1 == v2 and u1 == u2: if not transformation: return True elif transformation == "matrix": return True, matrix(ZZ, [[1, 0], [0, 1]]) else: return True, one # a necessary, but not sufficient condition unless N is square-free if v1.gcd(N) != v2.gcd(N): if not transformation: return False else: return False, None if (u1, v1) != (zero, one): if v1 in [zero, one]: s1 = one else: s1 = u1.inverse_mod(v1) else: s1 = 0 if (u2, v2) != (zero, one): if v2 in [zero, one]: s2 = one else: s2 = u2.inverse_mod(v2) else: s2 = zero g = (v1 * v2).gcd(N) a = s1 * v2 - s2 * v1 if a % g != 0: if not transformation: return False else: return False, None if not transformation: return True # Now we know the cusps are equivalent. Use the proof of Prop 2.2.3 # of Cremona to find a matrix in Gamma_0(N) relating them. if v1 == 0: # the first is oo if v2 == 0: # both are oo if transformation == "matrix": return (True, matrix(ZZ, [[1, 0], [0, 1]])) else: return (True, one) else: dum, s2, r2 = u2.xgcd(-v2) assert dum.is_one() if transformation == "matrix": return (True, matrix(ZZ, [[u2, r2], [v2, s2]])) else: return (True, u2) elif v2 == 0: # the second is oo dum, s1, r1 = u1.xgcd(-v1) assert dum.is_one() if transformation == "matrix": return (True, matrix(ZZ, [[s1, -r1], [-v1, u1]])) else: return (True, s1) dum, s2, r2 = u2.xgcd(-v2) assert dum.is_one() dum, s1, r1 = u1.xgcd(-v1) assert dum.is_one() a = s1 * v2 - s2 * v1 assert (a % g).is_zero() # solve x*v1*v2 + a = 0 (mod N). d, x0, y0 = (v1 * v2).xgcd(N) # x0*v1*v2 + y0*N = d = g. # so x0*v1*v2 - g = 0 (mod N) x = -x0 * ZZ(a / g) # now x*v1*v2 + a = 0 (mod N) # the rest is all added in trac #10926 s1p = s1 + x * v1 M = N // g if transformation == "matrix": C = s1p * v2 - s2 * v1 if C % (M * v1 * v2) == 0: k = -C // (M * v1 * v2) else: k = -(C / (M * v1 * v2)).round() s1pp = s1p + k * M * v1 # C += k*M*v1*v2 # is now the smallest in absolute value C = s1pp * v2 - s2 * v1 A = u2 * s1pp - r2 * v1 r1pp = r1 + (x + k * M) * u1 B = r2 * u1 - r1pp * u2 D = s2 * u1 - r1pp * v2 ga = matrix(ZZ, [[A, B], [C, D]]) assert ga.det() == 1 assert C % N == 0 assert (A * u1 + B * v1) / (C * u1 + D * v1) == u2 / v2 return (True, ga) else: # mainly for backwards compatibility and # for how it is used in modular symbols A = (u2 * s1p - r2 * v1) if u2 != 0 and v1 != 0: A = A % (u2 * v1 * M) return (True, A)
def cuspidal_ideal_generators(self, maxweight=8, prec=None): r""" Calculate generators for the ideal of cuspidal forms in this ring, as a module over the whole ring. EXAMPLES:: sage: ModularFormsRing(Gamma0(3)).cuspidal_ideal_generators(maxweight=12) [(6, q - 6*q^2 + 9*q^3 + 4*q^4 + O(q^5), q - 6*q^2 + 9*q^3 + 4*q^4 + 6*q^5 + O(q^6))] sage: [k for k,f,F in ModularFormsRing(13, base_ring=ZZ).cuspidal_ideal_generators(maxweight=14)] [4, 4, 4, 6, 6, 12] """ working_prec = self.modular_forms_of_weight(maxweight).sturm_bound() if self.__cached_cusp_maxweight > -1: k = self.__cached_cusp_maxweight + 1 verbose( "Already calculated cusp gens up to weight %s -- using those" % (k - 1)) # we may need to increase the precision of the cached cusp # generators G = [] for j, f, F in self.__cached_cusp_gens: if f.prec() >= working_prec: f = F.qexp(working_prec).change_ring(self.base_ring()) G.append((j, f, F)) else: k = 2 G = [] while k <= maxweight: t = verbose("Looking for cusp generators in weight %s" % k) kprec = self.modular_forms_of_weight(k).sturm_bound() flist = [] for (j, f, F) in G: for g in self.q_expansion_basis(k - j, prec=kprec): flist.append(g * f) A = self.base_ring()**kprec W = A.span([A(f.padded_list(kprec)) for f in flist]) S = self.modular_forms_of_weight(k).cuspidal_submodule() if (W.rank() == S.dimension() and (self.base_ring().is_field() or W.index_in_saturation() == 1)): verbose("Nothing new in weight %s" % k, t) k += 1 continue t = verbose( "Known cusp generators span a submodule of dimension %s of space of dimension %s" % (W.rank(), S.dimension()), t) B = S.q_integral_basis(prec=working_prec) V = A.span([ A(f.change_ring(self.base_ring()).padded_list(kprec)) for f in B ]) Q = V / W for q in Q.gens(): try: qc = V.coordinates(Q.lift(q)) except AttributeError: # work around a silly free module bug qc = V.coordinates(q.lift()) qcZZ = [ZZ(_) for _ in qc] # lift to ZZ so we can define F f = sum([B[i] * qcZZ[i] for i in range(len(B))]) F = S(f) G.append((k, f.change_ring(self.base_ring()), F)) verbose('added %s new generators' % Q.ngens(), t) k += 1 self.__cached_cusp_maxweight = maxweight self.__cached_cusp_gens = G if prec is None: return G elif prec <= working_prec: return [(k, f.truncate_powerseries(prec), F) for k, f, F in G] else: # user wants increased precision, so we may as well cache that Gnew = [(k, F.qexp(prec).change_ring(self.base_ring()), F) for k, f, F in G] self.__cached_cusp_gens = Gnew return Gnew
def is_gamma_h_equiv(self, other, G): r""" Return a pair (b, t), where b is True or False as self and other are equivalent under the action of G, and t is 1 or -1, as described below. Two cusps `u1/v1` and `u2/v2` are equivalent modulo Gamma_H(N) if and only if `v1 = h*v2 (\mathrm{mod} N)` and `u1 = h^{(-1)}*u2 (\mathrm{mod} gcd(v1,N))` or `v1 = -h*v2 (mod N)` and `u1 = -h^{(-1)}*u2 (\mathrm{mod} gcd(v1,N))` for some `h \in H`. Then t is 1 or -1 as c and c' fall into the first or second case, respectively. INPUT: - ``other`` - Cusp - ``G`` - a congruence subgroup Gamma_H(N) OUTPUT: - ``bool`` - True if self and other are equivalent - ``int`` - -1, 0, 1; extra info EXAMPLES:: sage: x = Cusp(2,3) sage: y = Cusp(4,5) sage: x.is_gamma_h_equiv(y,GammaH(13,[2])) (True, 1) sage: x.is_gamma_h_equiv(y,GammaH(13,[5])) (False, 0) sage: x.is_gamma_h_equiv(y,GammaH(5,[])) (False, 0) sage: x.is_gamma_h_equiv(y,GammaH(23,[4])) (True, -1) Enumerating the cusps for a space of modular symbols uses this function. :: sage: G = GammaH(25,[6]) ; M = G.modular_symbols() ; M Modular Symbols space of dimension 11 for Congruence Subgroup Gamma_H(25) with H generated by [6] of weight 2 with sign 0 and over Rational Field sage: M.cusps() [37/75, 1/2, 31/125, 1/4, -2/5, 2/5, -1/5, 1/10, -3/10, 1/15, 7/15, 9/20] sage: len(M.cusps()) 12 This is always one more than the associated space of weight 2 Eisenstein series. :: sage: G.dimension_eis(2) 11 sage: M.cuspidal_subspace() Modular Symbols subspace of dimension 0 of Modular Symbols space of dimension 11 for Congruence Subgroup Gamma_H(25) with H generated by [6] of weight 2 with sign 0 and over Rational Field sage: G.dimension_cusp_forms(2) 0 """ from sage.modular.arithgroup.all import is_GammaH if not isinstance(other, Cusp): other = Cusp(other) if not is_GammaH(G): raise TypeError("G must be a group GammaH(N).") H = G._list_of_elements_in_H() N = ZZ(G.level()) u1 = self.__a v1 = self.__b u2 = other.__a v2 = other.__b g = v1.gcd(N) for h in H: v_tmp = (h * v1) % N u_tmp = (h * u2) % N if (v_tmp - v2) % N == 0 and (u_tmp - u1) % g == 0: return True, 1 if (v_tmp + v2) % N == 0 and (u_tmp + u1) % g == 0: return True, -1 return False, 0
def _group_gens(self): r""" Return a set of generators of the group `S(K_0) / S(K_u)` (which is either `{\rm SL}_2(\ZZ / p^u \ZZ)` if the conductor is even, and a quotient of an Iwahori subgroup if the conductor is odd). EXAMPLES:: sage: from sage.modular.local_comp.type_space import example_type_space sage: example_type_space()._group_gens() [[1, 1, 0, 1], [0, -1, 1, 0]] sage: example_type_space(3)._group_gens() [[1, 1, 0, 1], [1, 0, 3, 1], [2, 0, 0, 5]] """ if (self.conductor() % 2) == 0: return [ [ZZ(1), ZZ(1), ZZ(0), ZZ(1)], [ZZ(0), ZZ(-1), ZZ(1), ZZ(0)] ] else: p = self.prime() if p == 2: return [ [ZZ(1), ZZ(1), ZZ(0), ZZ(1)], [ZZ(1), ZZ(0), ZZ(p), ZZ(1)] ] else: a = Zmod(p**(self.u() + 1))(ZZ(Zmod(p).unit_gens()[0])) return [ [ZZ(1), ZZ(1), ZZ(0), ZZ(1)], [ZZ(1), ZZ(0), ZZ(p), ZZ(1)], [ZZ(a), 0, 0, ZZ(~a)] ]
def __init__(self, group, base_ring=QQ): r""" The ring of modular forms (of weights 0 or at least 2) for a congruence subgroup of `{\rm SL}_2(\ZZ)`, with coefficients in a specified base ring. INPUT: - ``group`` -- a congruence subgroup of `{\rm SL}_2(\ZZ)`, or a positive integer `N` (interpreted as `\Gamma_0(N)`) - ``base_ring`` (ring, default: `\QQ`) -- a base ring, which should be `\QQ`, `\ZZ`, or the integers mod `p` for some prime `p`. EXAMPLES:: sage: ModularFormsRing(Gamma1(13)) Ring of modular forms for Congruence Subgroup Gamma1(13) with coefficients in Rational Field sage: m = ModularFormsRing(4); m Ring of modular forms for Congruence Subgroup Gamma0(4) with coefficients in Rational Field sage: m.modular_forms_of_weight(2) Modular Forms space of dimension 2 for Congruence Subgroup Gamma0(4) of weight 2 over Rational Field sage: m.modular_forms_of_weight(10) Modular Forms space of dimension 6 for Congruence Subgroup Gamma0(4) of weight 10 over Rational Field sage: m == loads(dumps(m)) True sage: m.generators() [(2, 1 + 24*q^2 + 24*q^4 + 96*q^6 + 24*q^8 + O(q^10)), (2, q + 4*q^3 + 6*q^5 + 8*q^7 + 13*q^9 + O(q^10))] sage: m.q_expansion_basis(2,10) [1 + 24*q^2 + 24*q^4 + 96*q^6 + 24*q^8 + O(q^10), q + 4*q^3 + 6*q^5 + 8*q^7 + 13*q^9 + O(q^10)] sage: m.q_expansion_basis(3,10) [] sage: m.q_expansion_basis(10,10) [1 + 10560*q^6 + 3960*q^8 + O(q^10), q - 8056*q^7 - 30855*q^9 + O(q^10), q^2 - 796*q^6 - 8192*q^8 + O(q^10), q^3 + 66*q^7 + 832*q^9 + O(q^10), q^4 + 40*q^6 + 528*q^8 + O(q^10), q^5 + 20*q^7 + 190*q^9 + O(q^10)] TESTS: Check that :trac:`15037` is fixed:: sage: ModularFormsRing(3.4) Traceback (most recent call last): ... ValueError: Group (=3.40000000000000) should be a congruence subgroup sage: ModularFormsRing(Gamma0(2), base_ring=PolynomialRing(ZZ,x)) Traceback (most recent call last): ... ValueError: Base ring (=Univariate Polynomial Ring in x over Integer Ring) should be QQ, ZZ or a finite prime field """ if isinstance(group, (int, long, Integer)): group = Gamma0(group) elif not is_CongruenceSubgroup(group): raise ValueError("Group (=%s) should be a congruence subgroup" % group) if base_ring != ZZ and not base_ring.is_prime_field(): raise ValueError( "Base ring (=%s) should be QQ, ZZ or a finite prime field" % base_ring) self.__group = group self.__base_ring = base_ring self.__cached_maxweight = ZZ(-1) self.__cached_gens = [] self.__cached_cusp_maxweight = ZZ(-1) self.__cached_cusp_gens = []
def rho(self, g): r""" Calculate the action of the group element `g` on the type space. EXAMPLES:: sage: from sage.modular.local_comp.type_space import example_type_space sage: T = example_type_space(2) sage: m = T.rho([2,0,0,1]); m [ 1 -2 1 0] [ 1 -1 0 1] [ 1 0 -1 1] [ 0 1 -2 1] sage: v = T.eigensymbol_subspace().basis()[0] sage: m * v == v True We test that it is a left action:: sage: T = example_type_space(0) sage: a = [0,5,4,3]; b = [0,2,3,5]; ab = [1,4,2,2] sage: T.rho(ab) == T.rho(a) * T.rho(b) True An odd level example:: sage: from sage.modular.local_comp.type_space import TypeSpace sage: T = TypeSpace(Newform('54a'), 3) sage: a = [0,1,3,0]; b = [2,1,0,1]; ab = [0,1,6,3] sage: T.rho(ab) == T.rho(a) * T.rho(b) True """ if not self.is_minimal(): raise NotImplementedError( "Group action on non-minimal type space not implemented" ) if self.u() == 0: # silly special case: rep is principal series or special, so SL2 # action on type space is trivial raise ValueError( "Representation is not supercuspidal" ) p = self.prime() f = p**self.u() g = [ZZ(_) for _ in g] d = (g[0]*g[3] - g[2]*g[1]) # g is in S(K_0) (easy case) if d % f == 1: return self._rho_s(g) # g is in K_0, but not in S(K_0) if d % p != 0: try: a = self._a except AttributeError: self._discover_torus_action() a = self._a i = 0 while (d * a**i) % f != 1: i += 1 if i > f: raise ArithmeticError return self._rho_s([a**i*g[0], g[1], a**i*g[2], g[3]]) * self._amat**(-i) # funny business if (self.conductor() % 2 == 0): if all([x.valuation(p) > 0 for x in g]): eps = self.form().character()(crt(1, p, f, self.tame_level())) return ~eps * self.rho([x // p for x in g]) else: raise ArithmeticError( "g(={0}) not in K".format(g) ) else: m = matrix(ZZ, 2, g) s = m.det().valuation(p) mm = (matrix(QQ, 2, [0, -1, p, 0])**(-s) * m).change_ring(ZZ) return self._unif_ramified()**s * self.rho(mm.list())
def hecke_stable_subspace(chi, aux_prime=ZZ(2)): r""" Compute a q-expansion basis for S_1(chi). Results are returned as q-expansions to a certain fixed (and fairly high) precision. If more precision is required this can be obtained with :func:`modular_ratio_to_prec`. EXAMPLES:: sage: from sage.modular.modform.weight1 import hecke_stable_subspace sage: hecke_stable_subspace(DirichletGroup(59, QQ).0) [q - q^3 + q^4 - q^5 - q^7 - q^12 + q^15 + q^16 + 2*q^17 - q^19 - q^20 + q^21 + q^27 - q^28 - q^29 + q^35 + O(q^40)] """ from sage.modular.modform.constructor import EisensteinForms if chi(-1) == 1: return [] N = chi.modulus() chi = chi.minimize_base_ring() K = chi.base_ring() # Auxiliary prime for Hecke stability method l = aux_prime while l.divides(N): l = l.next_prime() verbose("Auxilliary prime: %s" % l, level=1) # Compute working precision R = l * Gamma0(N).sturm_bound(l + 2) t = verbose("Computing modular ratio space", level=1) mrs = modular_ratio_space(chi) t = verbose("Computing modular ratios to precision %s" % R, level=1) qexps = [modular_ratio_to_prec(chi, f, R) for f in mrs] verbose("Done", t=t, level=1) # We want to compute the largest subspace of I stable under T_l. To do # this, we compute I intersect T_l(I) modulo q^(R/l), and take its preimage # under T_l, which is then well-defined modulo q^R. from sage.modular.modform.hecke_operator_on_qexp import hecke_operator_on_qexp t = verbose("Computing Hecke-stable subspace", level=1) A = PowerSeriesRing(K, 'q') r = R // l V = K**R W = K**r Tl_images = [hecke_operator_on_qexp(f, l, 1, chi) for f in qexps] qvecs = [V(x.padded_list(R)) for x in qexps] qvecs_trunc = [W(x.padded_list(r)) for x in qexps] Tvecs = [W(x.padded_list(r)) for x in Tl_images] I = V.submodule(qvecs) Iimage = W.span(qvecs_trunc) TlI = W.span(Tvecs) Jimage = Iimage.intersection(TlI) J = I.Hom(W)(Tvecs).inverse_image(Jimage) verbose("Hecke-stable subspace is %s-dimensional" % J.dimension(), t=t, level=1) if J.rank() == 0: return [] # The theory does not guarantee that J is exactly S_1(chi), just that it is # intermediate between S_1(chi) and M_1(chi). In every example I know of, # it is equal to S_1(chi), but just for honesty, we check this anyway. t = verbose("Checking cuspidality", level=1) JEis = V.span( V(x.padded_list(R)) for x in EisensteinForms(chi, 1).q_echelon_basis(prec=R)) D = JEis.intersection(J) if D.dimension() != 0: raise ArithmeticError("Got non-cuspidal form!") verbose("Done", t=t, level=1) qexps = Sequence(A(x.list()).add_bigoh(R) for x in J.gens()) return qexps
def CyclicSievingPolynomial(L, cyc_act=None, order=None, get_order=False): """ Returns the unique polynomial p of degree smaller than order such that the triple ( L, cyc_act, p ) exhibits the CSP. If ``cyc_act`` is None, ``L`` is expected to contain the orbit lengths. INPUT: - L -- if cyc_act is None: list of orbit sizes, otherwise list of objects - cyc_act -- (default:None) function taking an element of L and returning an element of L (must define a bijection on L) - order -- (default:None) if set to an integer, this cyclic order of cyc_act is used (must be an integer multiple of the order of cyc_act) otherwise, the order of cyc_action is used - get_order -- (default:False) if True, a tuple [p,n] is returned where p as above, and n is the order EXAMPLES:: sage: from sage.combinat.cyclic_sieving_phenomenon import CyclicSievingPolynomial sage: S42 = [ Set(S) for S in subsets([1,2,3,4]) if len(S) == 2 ]; S42 [{1, 2}, {1, 3}, {2, 3}, {1, 4}, {2, 4}, {3, 4}] sage: cyc_act = lambda S: Set( i.mod(4)+1 for i in S) sage: cyc_act([1,3]) {2, 4} sage: cyc_act([1,4]) {1, 2} sage: CyclicSievingPolynomial( S42, cyc_act ) q^3 + 2*q^2 + q + 2 sage: CyclicSievingPolynomial( S42, cyc_act, get_order=True ) [q^3 + 2*q^2 + q + 2, 4] sage: CyclicSievingPolynomial( S42, cyc_act, order=8 ) q^6 + 2*q^4 + q^2 + 2 sage: CyclicSievingPolynomial([4,2]) q^3 + 2*q^2 + q + 2 TESTS: We check that :trac:`13997` is handled:: sage: CyclicSievingPolynomial( S42, cyc_act, order=8, get_order=True ) [q^6 + 2*q^4 + q^2 + 2, 8] """ if cyc_act: orbits = orbit_decomposition(L, cyc_act) else: orbits = [list(range(k)) for k in L] R = QQ['q'] q = R.gen() p = R(0) orbit_sizes = {} for orbit in orbits: l = len(orbit) if l in orbit_sizes: orbit_sizes[l] += 1 else: orbit_sizes[l] = 1 n = lcm(list(orbit_sizes)) if order: if order.mod(n) != 0: raise ValueError( "The given order is not valid as it is not a multiple of the order of the given cyclic action" ) else: order = n for i in range(n): if i == 0: j = sum(orbit_sizes.values()) else: j = sum(orbit_sizes[l] for l in orbit_sizes if ZZ(i).mod(n / l) == 0) p += j * q**i p = p(q**ZZ(order / n)) if get_order: return [p, order] else: return p
def chebyshev_polynomial(self, n, kind='first', monic=False): """ Generates an endomorphism of this affine line by a Chebyshev polynomial. Chebyshev polynomials are a sequence of recursively defined orthogonal polynomials. Chebyshev of the first kind are defined as `T_0(x) = 1`, `T_1(x) = x`, and `T_{n+1}(x) = 2xT_n(x) - T_{n-1}(x)`. Chebyshev of the second kind are defined as `U_0(x) = 1`, `U_1(x) = 2x`, and `U_{n+1}(x) = 2xU_n(x) - U_{n-1}(x)`. INPUT: - ``n`` -- a non-negative integer. - ``kind`` -- ``first`` or ``second`` specifying which kind of chebyshev the user would like to generate. Defaults to ``first``. - ``monic`` -- ``True`` or ``False`` specifying if the polynomial defining the system should be monic or not. Defaults to ``False``. OUTPUT: :class:`DynamicalSystem_affine` EXAMPLES:: sage: A.<x> = AffineSpace(QQ, 1) sage: A.chebyshev_polynomial(5, 'first') Dynamical System of Affine Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x) to (16*x^5 - 20*x^3 + 5*x) :: sage: A.<x> = AffineSpace(QQ, 1) sage: A.chebyshev_polynomial(3, 'second') Dynamical System of Affine Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x) to (8*x^3 - 4*x) :: sage: A.<x> = AffineSpace(QQ, 1) sage: A.chebyshev_polynomial(3, 2) Traceback (most recent call last): ... ValueError: keyword 'kind' must have a value of either 'first' or 'second' :: sage: A.<x> = AffineSpace(QQ, 1) sage: A.chebyshev_polynomial(-4, 'second') Traceback (most recent call last): ... ValueError: first parameter 'n' must be a non-negative integer :: sage: A = AffineSpace(QQ, 2, 'x') sage: A.chebyshev_polynomial(2) Traceback (most recent call last): ... TypeError: affine space must be of dimension 1 :: sage: A.<x> = AffineSpace(QQ, 1) sage: A.chebyshev_polynomial(7, monic=True) Dynamical System of Affine Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x) to (x^7 - 7*x^5 + 14*x^3 - 7*x) :: sage: F.<t> = FunctionField(QQ) sage: A.<x> = AffineSpace(F,1) sage: A.chebyshev_polynomial(4, monic=True) Dynamical System of Affine Space of dimension 1 over Rational function field in t over Rational Field Defn: Defined on coordinates by sending (x) to (x^4 + (-4)*x^2 + 2) """ if self.dimension_relative() != 1: raise TypeError("affine space must be of dimension 1") n = ZZ(n) if (n < 0): raise ValueError( "first parameter 'n' must be a non-negative integer") from sage.dynamics.arithmetic_dynamics.affine_ds import DynamicalSystem_affine if kind == 'first': if monic and self.base().characteristic() != 2: f = DynamicalSystem_affine([chebyshev_T(n, self.gen(0))], domain=self) f = f.homogenize(1) f = f.conjugate(matrix([[1 / ZZ(2), 0], [0, 1]])) f = f.dehomogenize(1) return f return DynamicalSystem_affine([chebyshev_T(n, self.gen(0))], domain=self) elif kind == 'second': if monic and self.base().characteristic() != 2: f = DynamicalSystem_affine([chebyshev_T(n, self.gen(0))], domain=self) f = f.homogenize(1) f = f.conjugate(matrix([[1 / ZZ(2), 0], [0, 1]])) f = f.dehomogenize(1) return f return DynamicalSystem_affine([chebyshev_U(n, self.gen(0))], domain=self) else: raise ValueError( "keyword 'kind' must have a value of either 'first' or 'second'" )
def _siegel_modular_forms_generators(parent, prec=None, degree=0): r""" Compute the four Igusa generators of the ring of Siegel modular forms of degree 2 and level 1 and even weight (this happens if weights='even'). If weights = 'all' you get the Siegel modular forms of degree 2, level 1 and even and odd weight. EXAMPLES:: sage: A, B, C, D = SiegelModularFormsAlgebra().gens() sage: C[(2, 0, 9)] -390420 sage: D[(4, 2, 5)] 17689760 sage: A2, B2, C2, D2 = SiegelModularFormsAlgebra().gens(prec=50) sage: A[(1, 0, 1)] == A2[(1, 0, 1)] True sage: A3, B3, C3, D3 = SiegelModularFormsAlgebra().gens(prec=500) sage: B2[(2, 1, 3)] == B3[(2, 1, 3)] True TESTS:: sage: from sage.modular.siegel.siegel_modular_forms_algebra import _siegel_modular_forms_generators sage: S = SiegelModularFormsAlgebra() sage: S.gens() == _siegel_modular_forms_generators(S) True """ group = parent.group() weights = parent.weights() if prec is None: prec = parent.default_prec() if group == 'Sp(4,Z)' and 0 == degree: from sage.modular.all import ModularForms E4 = ModularForms(1, 4).gen(0) M6 = ModularForms(1, 6) E6 = ModularForms(1, 6).gen(0) M8 = ModularForms(1, 8) M10 = ModularForms(1, 10) Delta = ModularForms(1, 12).cuspidal_subspace().gen(0) M14 = ModularForms(1, 14) A = SiegelModularForm(60 * E4, M6(0), prec=prec, name='Igusa_4') B = SiegelModularForm(-84 * E6, M8(0), prec=prec, name='Igusa_6') C = SiegelModularForm(M10(0), -Delta, prec=prec, name='Igusa_10') D = SiegelModularForm(Delta, M14(0), prec=prec, name='Igusa_12') # TODO: the base_ring of A, B, ... should be ZZ # Here a hack for now: a = [A, B, C, D] b = [] from sage.rings.all import ZZ for F in a: c = F.coeffs() for f in c.keys(): c[f] = ZZ(c[f]) F = parent.element_class(parent=parent, weight=F.weight(), coeffs=c, prec=prec, name=F.name()) b.append(F) if weights == 'even': return b if weights == 'all': from .fastmult import chi35 coeffs35 = chi35(prec, b[0], b[1], b[2], b[3]) from sage.groups.all import KleinFourGroup G = KleinFourGroup() from sage.algebras.all import GroupAlgebra R = GroupAlgebra(G) det = R(G.gen(0)) E = parent.element_class(parent=parent, weight=35, coeffs=coeffs35, prec=prec, name='Delta_35') E = E * det b.append(E) return b raise ValueError( "weights = '{0}': should be 'all' or 'even'".format(weights)) if group == 'Sp(4,Z)' and weights == 'even' and 2 == degree: b = _siegel_modular_forms_generators(parent=parent) c = [] for F in b: i = b.index(F) for G in b[(i + 1):]: c.append(F.satoh_bracket(G)) return c raise NotImplementedError("Not yet implemented")