Beispiel #1
0
def test_ellrains(pbound = 2**62, nbound = 100):
    import sys
    from sage.sets.primes import Primes
    for p in Primes():
        if p < 4:
            continue
        if p > pbound:
            break
        for n in xrange(2, nbound):
            k = GF(p**n, name='z')
            K = FiniteField_flint_fq_nmod(p, k.modulus(), name='z')
            try:
                a, b = find_gens(K, K)
                print "Testing p = {} and n = {}".format(p, n)
                print "Computing minpol...",
                sys.stdout.flush()
                f = a.minpoly()
                assert f.degree() == n
                g = b.minpoly()
                assert f == g
                print "done"
            except RuntimeError:
                pass
Beispiel #2
0
    def multiple_of_order_using_frobp(self, maxp=None):
        """
        Return a multiple of the order of this torsion group.

        In the `Gamma_0` case, the multiple is computed using characteristic
        polynomials of Hecke operators of odd index not dividing the level. In
        the `Gamma_1` case, the multiple is computed by expressing the
        frobenius polynomial in terms of the characteristic polynomial of left
        multiplication by `a_p` for odd primes p not dividing the level.

        INPUT:


        -  ``maxp`` - (default: None) If maxp is None (the
           default), return gcd of best bound computed so far with bound
           obtained by computing GCD's of orders modulo p until this gcd
           stabilizes for 3 successive primes. If maxp is given, just use all
           primes up to and including maxp.


        EXAMPLES::

            sage: J = J0(11)
            sage: G = J.rational_torsion_subgroup()
            sage: G.multiple_of_order_using_frobp(11)
            5

        Increasing maxp may yield a tighter bound. If maxp=None, then Sage
        will use more primes until the multiple stabilizes for 3 successive
        primes.  ::

            sage: J = J0(389)
            sage: G = J.rational_torsion_subgroup(); G
            Torsion subgroup of Abelian variety J0(389) of dimension 32
            sage: G.multiple_of_order_using_frobp()
            97
            sage: [G.multiple_of_order_using_frobp(p) for p in prime_range(3,11)]
            [92645296242160800, 7275, 291]
            sage: [G.multiple_of_order_using_frobp(p) for p in prime_range(3,13)]
            [92645296242160800, 7275, 291, 97]
            sage: [G.multiple_of_order_using_frobp(p) for p in prime_range(3,19)]
            [92645296242160800, 7275, 291, 97, 97, 97]

        We can compute the multiple of order of the torsion subgroup for Gamma0
        and Gamma1 varieties, and their products. ::

            sage: A = J0(11) * J0(33)
            sage: A.rational_torsion_subgroup().multiple_of_order_using_frobp()
            1000

            sage: A = J1(23)
            sage: A.rational_torsion_subgroup().multiple_of_order_using_frobp()
            9406793
            sage: A.rational_torsion_subgroup().multiple_of_order_using_frobp(maxp=50)
            408991

            sage: A = J1(19) * J0(21)
            sage: A.rational_torsion_subgroup().multiple_of_order_using_frobp()
            35064

        The next example illustrates calling this function with a larger
        input and how the result may be cached when maxp is None::

            sage: T = J0(43)[1].rational_torsion_subgroup()
            sage: T.multiple_of_order_using_frobp()
            14
            sage: T.multiple_of_order_using_frobp(50)
            7
            sage: T.multiple_of_order_using_frobp()
            7

        This function is not implemented for general congruence subgroups
        unless the dimension is zero. ::

            sage: A = JH(13,[2]); A
            Abelian variety J0(13) of dimension 0
            sage: A.rational_torsion_subgroup().multiple_of_order_using_frobp()
            1

            sage: A = JH(15, [2]); A
            Abelian variety JH(15,[2]) of dimension 1
            sage: A.rational_torsion_subgroup().multiple_of_order_using_frobp()
            Traceback (most recent call last):
            ...
            NotImplementedError: torsion multiple only implemented for Gamma0 and Gamma1
        """
        if maxp is None:
            try:
                return self.__multiple_of_order_using_frobp
            except AttributeError:
                pass
        A = self.abelian_variety()
        if A.dimension() == 0:
            T = ZZ(1)
            self.__multiple_of_order_using_frobp = T
            return T
        if not all((is_Gamma0(G) or is_Gamma1(G) for G in A.groups())):
            raise NotImplementedError("torsion multiple only implemented for Gamma0 and Gamma1")

        bnd = ZZ(0)
        N = A.level()
        cnt = 0
        if maxp is None:
            X = Primes()
        else:
            X = prime_range(maxp+1)
        for p in X:
            if (2*N) % p == 0:
                continue

            if (len(A.groups()) == 1 and is_Gamma0(A.groups()[0])):
                f = A.hecke_polynomial(p)
                b = ZZ(f(p+1))
            else:
                from .constructor import AbelianVariety
                D = [AbelianVariety(f) for f in
                     A.newform_decomposition('a')]
                b = 1
                for simple in D:
                    G = simple.newform_level()[1]
                    if is_Gamma0(G):
                        f = simple.hecke_polynomial(p)
                        b *= ZZ(f(p+1))
                    else:
                        f = simple.newform('a')
                        Kf = f.base_ring()
                        eps = f.character()
                        Qe = eps.base_ring()

                        if Kf != QQ:
                            # relativize number fields to compute charpoly of
                            # left multiplication of ap on Kf as a Qe-vector
                            # space.
                            Lf = Kf.relativize(Qe.gen(), 'a')
                            to_Lf = Lf.structure()[1]

                            name = Kf._names[0]
                            ap = to_Lf(f.modular_symbols(1).eigenvalue(p, name))

                            G_ps = ap.matrix().charpoly()
                            b *= ZZ(Qe(G_ps(1 + to_Lf(eps(p))*p)).norm())
                        else:
                            ap = f.modular_symbols(1).eigenvalue(p)
                            b *= ZZ(1 + eps(p)*p - ap)

            if bnd == 0:
                bnd = b
            else:
                bnd_last = bnd
                bnd = ZZ(gcd(bnd, b))
                if bnd == bnd_last:
                    cnt += 1
                else:
                    cnt = 0
                if maxp is None and cnt >= 2:
                    break

        # The code below caches the computed bound and
        # will be used if this function is called
        # again with maxp equal to None (the default).
        if maxp is None:
            # maxp is None but self.__multiple_of_order_using_frobp  is
            # not set, since otherwise we would have immediately
            # returned at the top of this function
            self.__multiple_of_order_using_frobp = bnd
        else:
            # maxp is given -- record new info we get as
            # a gcd...
            try:
                self.__multiple_of_order_using_frobp = \
                        gcd(self.__multiple_of_order_using_frobp, bnd)
            except AttributeError:
                # ... except in the case when
                # self.__multiple_of_order_using_frobp was never set.  In this
                # case, we just set it as long as the gcd stabilized for 3 in a
                # row.
                if cnt >= 2:
                    self.__multiple_of_order_using_frobp = bnd
        return bnd
Beispiel #3
0
def FinitelyGeneratedHeisenbergPresentation(n=1, p=0):
    r"""
    Return a finite presentation of the Heisenberg group.

    The Heisenberg group is the group of `(n+2) \times (n+2)` matrices
    over a ring `R` with diagonal elements equal to 1, first row and
    last column possibly nonzero, and all the other entries equal to zero.

    INPUT:

    - ``n`` -- the degree of the Heisenberg group

    - ``p`` -- (optional) a prime number, where we construct the
      Heisenberg group over the finite field `\ZZ/p\ZZ`
 
    OUTPUT:

    Finitely generated Heisenberg group over the finite field
    of order ``p`` or over the integers.

    .. SEEALSO::

        :class:`~sage.groups.matrix_gps.heisenberg.HeisenbergGroup`

    EXAMPLES::

        sage: H = groups.presentation.Heisenberg(); H
        Finitely presented group < x1, y1, z |
         x1*y1*x1^-1*y1^-1*z^-1, z*x1*z^-1*x1^-1, z*y1*z^-1*y1^-1 >
        sage: H.order()
        +Infinity
        sage: r1, r2, r3 = H.relations()
        sage: A = matrix([[1, 1, 0], [0, 1, 0], [0, 0, 1]])
        sage: B = matrix([[1, 0, 0], [0, 1, 1], [0, 0, 1]])
        sage: C = matrix([[1, 0, 1], [0, 1, 0], [0, 0, 1]])
        sage: r1(A, B, C)
        [1 0 0]
        [0 1 0]
        [0 0 1]
        sage: r2(A, B, C)
        [1 0 0]
        [0 1 0]
        [0 0 1]
        sage: r3(A, B, C)
        [1 0 0]
        [0 1 0]
        [0 0 1]
        sage: p = 3
        sage: Hp = groups.presentation.Heisenberg(p=3)
        sage: Hp.order() == p**3 
        True
        sage: Hnp = groups.presentation.Heisenberg(n=2, p=3)
        sage: len(Hnp.relations())
        13

    REFERENCES:

    - :wikipedia:`Heisenberg_group`
    """
    n = Integer(n)
    if n < 1:
        raise ValueError('n must be a positive integer')

    # generators' names are x1, .., xn, y1, .., yn, z
    vx = ['x' + str(i) for i in range(1, n + 1)]
    vy = ['y' + str(i) for i in range(1, n + 1)]
    str_generators = ', '.join(vx + vy + ['z'])

    F = FreeGroup(str_generators)
    x = F.gens()[0:n]  # list of generators x1, x2, ..., xn
    y = F.gens()[n:2 * n]  # list of generators x1, x2, ..., xn
    z = F.gen(n * 2)

    def commutator(a, b):
        return a * b * a**-1 * b**-1

    # First set of relations: [xi, yi] = z
    r1 = [commutator(x[i], y[i]) * z**-1 for i in range(n)]
    # Second set of relations: [z, xi] = 1
    r2 = [commutator(z, x[i]) for i in range(n)]
    # Third set of relations: [z, yi] = 1
    r3 = [commutator(z, y[i]) for i in range(n)]
    # Fourth set of relations: [xi, yi] = 1 for i != j
    r4 = [commutator(x[i], y[j]) for i in range(n) for j in range(n) if i != j]
    rls = r1 + r2 + r3 + r4

    from sage.sets.primes import Primes
    if p not in Primes() and p != 0:
        raise ValueError("p must be 0 or a prime number")
    if p > 0:
        rls += [w**p for w in F.gens()]
    return FinitelyPresentedGroup(F, tuple(rls))
Beispiel #4
0
    def hilbert_symbol_negative_at_S(self, S, b, check=True):
        r"""
        Returns an integer that has a negative Hilbert symbol with respect
        to a given rational number and a given set of primes (or places).

        The function is algorithm 3.4.1 in [Kir2016]_. It finds an integer `a`
        that has negative Hilbert symbol with respect to a given rational number
        exactly at a given set of primes (or places).

        INPUT:

        - ``S`` -- a list of rational primes, the infinite place as real
          embedding of `\QQ` or as -1
        - ``b`` -- a non-zero rational number which is a non-square locally
          at every prime in ``S``.
        - ``check`` -- ``bool`` (default:``True``) perform additional checks on
          input and confirm the output.

        OUTPUT:

        - An integer `a` that has negative Hilbert symbol `(a,b)_p` for
          every place `p` in `S` and no other place.

        EXAMPLES::

            sage: QQ.hilbert_symbol_negative_at_S([-1,5,3,2,7,11,13,23], -10/7)
            -9867
            sage: QQ.hilbert_symbol_negative_at_S([3, 5, QQ.places()[0], 11], -15)
            -33
            sage: QQ.hilbert_symbol_negative_at_S([3, 5], 2)
            15

        TESTS::

            sage: QQ.hilbert_symbol_negative_at_S(5/2, -2)
            Traceback (most recent call last):
            ...
            TypeError: first argument must be a list or integer

        ::

            sage: QQ.hilbert_symbol_negative_at_S([1, 3], 0)
            Traceback (most recent call last):
            ...
            ValueError: second argument must be nonzero

        ::

            sage: QQ.hilbert_symbol_negative_at_S([-1, 3, 5], 2)
            Traceback (most recent call last):
            ...
            ValueError: list should be of even cardinality

        ::

            sage: QQ.hilbert_symbol_negative_at_S([1, 3], 2)
            Traceback (most recent call last):
            ...
            ValueError: all entries in list must be prime or -1 for
            infinite place

        ::

            sage: QQ.hilbert_symbol_negative_at_S([5, 7], 2)
            Traceback (most recent call last):
            ...
            ValueError: second argument must be a nonsquare with
            respect to every finite prime in the list

        ::

            sage: QQ.hilbert_symbol_negative_at_S([1, 3], sqrt(2))
            Traceback (most recent call last):
            ...
            TypeError: second argument must be a rational number

        ::

            sage: QQ.hilbert_symbol_negative_at_S([-1, 3], 2)
            Traceback (most recent call last):
            ...
            ValueError: if the infinite place is in the list, the second
            argument must be negative

        AUTHORS:

        - Simon Brandhorst, Juanita Duque, Anna Haensch, Manami Roy, Sandi Rudzinski (10-24-2017)

        """
        from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF
        from sage.rings.padics.factory import Qp
        from sage.modules.free_module import VectorSpace
        from sage.matrix.constructor import matrix
        from sage.sets.primes import Primes
        from sage.arith.misc import hilbert_symbol, is_prime

        # input checks
        if not type(S) is list:
            raise TypeError("first argument must be a list or integer")
        # -1 is used for the infinite place
        infty = -1
        for i in range(len(S)):
            if S[i] == self.places()[0]:
                S[i] = -1
        if not b in self:
            raise TypeError("second argument must be a rational number")
        b = self(b)
        if b == 0:
            raise ValueError("second argument must be nonzero")
        if len(S) % 2:
            raise ValueError("list should be of even cardinality")
        for p in S:
            if p != infty:
                if check and not is_prime(p):
                    raise ValueError("all entries in list must be prime"
                                     " or -1 for infinite place")
                R = Qp(p)
                if R(b).is_square():
                    raise ValueError(
                        "second argument must be a nonsquare with"
                        " respect to every finite prime in the list")
            elif b > 0:
                raise ValueError("if the infinite place is in the list, "
                                 "the second argument must be negative")
        # L is the list of primes that we need to consider, b must have
        # nonzero valuation for each prime in L, this is the set S'
        # in Kirschmer's algorithm
        L = []
        L = [p[0] for p in b.factor() if p[0] not in S]
        # We must also consider 2 to be in L
        if 2 not in L and 2 not in S:
            L.append(2)
        # This adds the infinite place to L
        if b < 0 and infty not in S:
            L.append(infty)

        P = S + L
        # This constructs the vector v in the algorithm. This is the vector
        # that we are searching for. It represents the case when the Hilbert
        # symbol is negative for all primes in S and positive
        # at all primes in S'
        V = VectorSpace(GF(2), len(P))
        v = V([1] * len(S) + [0] * len(L))

        # Compute the map phi of Hilbert symbols at all the primes
        # in S and S'
        # For technical reasons, a Hilbert symbol of -1 is
        # respresented as 1 and a Hilbert symbol of 1
        # is represented as 0
        def phi(x):
            v = [(1 - hilbert_symbol(x, b, p)) // 2 for p in P]
            return V(v)

        M = matrix(GF(2), [phi(p) for p in P + [-1]])
        # We search through all the primes
        for q in Primes():
            # Only look at this prime if it is not in our list
            if q in P:
                continue

            # The algorithm terminates when the vector v is in the
            # subspace of V generated by the image of the phi map
            # on the set of generators
            w = phi(q)
            W = M.stack(matrix(w))
            if v in W.row_space():
                break
        Pq = P + [-1] + [q]
        l = W.solve_left(v)
        a = self.prod([Pq[i]**ZZ(l[i]) for i in range(l.degree())])
        if check:
            assert phi(a) == v, "oops"
        return a
Beispiel #5
0
    def multiple_of_order(self, maxp=None):
        """
        Return a multiple of the order of this torsion group.

        The multiple is computed using characteristic polynomials of Hecke
        operators of odd index not dividing the level.

        INPUT:


        -  ``maxp`` - (default: None) If maxp is None (the
           default), return gcd of best bound computed so far with bound
           obtained by computing GCD's of orders modulo p until this gcd
           stabilizes for 3 successive primes. If maxp is given, just use all
           primes up to and including maxp.


        EXAMPLES::

            sage: J = J0(11)
            sage: G = J.rational_torsion_subgroup()
            sage: G.multiple_of_order(11)
            5
            sage: J = J0(389)
            sage: G = J.rational_torsion_subgroup(); G
            Torsion subgroup of Abelian variety J0(389) of dimension 32
            sage: G.multiple_of_order()
            97
            sage: [G.multiple_of_order(p) for p in prime_range(3,11)]
            [92645296242160800, 7275, 291]
            sage: [G.multiple_of_order(p) for p in prime_range(3,13)]
            [92645296242160800, 7275, 291, 97]
            sage: [G.multiple_of_order(p) for p in prime_range(3,19)]
            [92645296242160800, 7275, 291, 97, 97, 97]

        ::

            sage: J = J0(33) * J0(11) ; J.rational_torsion_subgroup().order()
            Traceback (most recent call last):
            ...
            NotImplementedError: torsion multiple only implemented for Gamma0

        The next example illustrates calling this function with a larger
        input and how the result may be cached when maxp is None::

            sage: T = J0(43)[1].rational_torsion_subgroup()
            sage: T.multiple_of_order()
            14
            sage: T.multiple_of_order(50)
            7
            sage: T.multiple_of_order()
            7
        """
        if maxp is None:
            try:
                return self.__multiple_of_order
            except AttributeError:
                pass
        bnd = ZZ(0)
        A = self.abelian_variety()
        if A.dimension() == 0:
            T = ZZ(1)
            self.__multiple_of_order = T
            return T
        N = A.level()
        if not (len(A.groups()) == 1 and is_Gamma0(A.groups()[0])):
            # to generalize to this case, you'll need to
            # (1) define a charpoly_of_frob function:
            #       this is tricky because I don't know a simple
            #       way to do this for Gamma1 and GammaH.  Will
            #       probably have to compute explicit matrix for
            #       <p> operator (add to modular symbols code),
            #       then compute some charpoly involving
            #       that directly...
            # (2) use (1) -- see my MAGMA code.
            raise NotImplementedError(
                "torsion multiple only implemented for Gamma0")
        cnt = 0
        if maxp is None:
            X = Primes()
        else:
            X = prime_range(maxp + 1)
        for p in X:
            if (2 * N) % p == 0:
                continue

            f = A.hecke_polynomial(p)
            b = ZZ(f(p + 1))

            if bnd == 0:
                bnd = b
            else:
                bnd_last = bnd
                bnd = ZZ(gcd(bnd, b))
                if bnd == bnd_last:
                    cnt += 1
                else:
                    cnt = 0
                if maxp is None and cnt >= 2:
                    break

        # The code below caches the computed bound and
        # will be used if this function is called
        # again with maxp equal to None (the default).
        if maxp is None:
            # maxp is None but self.__multiple_of_order  is
            # not set, since otherwise we would have immediately
            # returned at the top of this function
            self.__multiple_of_order = bnd
        else:
            # maxp is given -- record new info we get as
            # a gcd...
            try:
                self.__multiple_of_order = gcd(self.__multiple_of_order, bnd)
            except AttributeError:
                # ... except in the case when self.__multiple_of_order
                # was never set.  In this case, we just set
                # it as long as the gcd stabilized for 3 in a row.
                if cnt >= 2:
                    self.__multiple_of_order = bnd
        return bnd
Beispiel #6
0
def p_saturation(Plist, p, sieve=True, lin_combs=dict(), verbose=False):
    r"""
    Checks whether the list of points is `p`-saturated.

    INPUT:

    - ``Plist`` (list) - a list of independent points on one elliptic curve.

    - ``p`` (integer) - a prime number.

    - ``sieve`` (boolean) - if True, use a sieve (when there are at
      least 2 points); otherwise test all combinations.

    - ``lin_combs`` (dict) - a dict, possibly empty, with keys
      coefficient tuples and values the corresponding linear
      combinations of the points in ``Plist``.

    .. note::

       The sieve is much more efficient when the points are saturated
       and the number of points or the prime are large.

    OUTPUT:

    Either (``True``, ``lin_combs``) if the points are `p`-saturated,
    or (``False``, ``i``, ``newP``) if they are not `p`-saturated, in
    which case after replacing the i'th point with ``newP``, the
    subgroup generated contains that generated by ``Plist`` with
    index `p`.  Note that while proving the points `p`-saturated, the
    ``lin_combs`` dict may have been enlarged, so is returned.

    EXAMPLES::

        sage: from sage.schemes.elliptic_curves.saturation import p_saturation
        sage: E = EllipticCurve('389a')
        sage: K.<i> = QuadraticField(-1)
        sage: EK = E.change_ring(K)
        sage: P = EK(1+i,-1-2*i)
        sage: p_saturation([P],2)
        (True, {})
        sage: p_saturation([2*P],2)
        (False, 0, (i + 1 : -2*i - 1 : 1))


        sage: Q = EK(0,0)
        sage: R = EK(-1,1)
        sage: p_saturation([P,Q,R],3)
        (True, {})

    Here we see an example where 19-saturation is proved, with the
    verbose flag set to True so that we can see what is going on::

        sage: p_saturation([P,Q,R],19, verbose=True)
        Using sieve method to saturate...
        There is 19-torsion modulo Fractional ideal (i + 14), projecting points
         --> [(184 : 27 : 1), (0 : 0 : 1), (196 : 1 : 1)]
         --rank is now 1
        There is 19-torsion modulo Fractional ideal (i - 14), projecting points
         --> [(15 : 168 : 1), (0 : 0 : 1), (196 : 1 : 1)]
         --rank is now 2
        There is 19-torsion modulo Fractional ideal (-2*i + 17), projecting points
         --> [(156 : 275 : 1), (0 : 0 : 1), (292 : 1 : 1)]
         --rank is now 3
        Reached full rank: points were 19-saturated
        (True, {})

    An example where the points are not 11-saturated::

        sage: res = p_saturation([P+5*Q,P-6*Q,R],11); res
        (False,
        0,
        (-5783311/14600041*i + 1396143/14600041 : 37679338314/55786756661*i + 3813624227/55786756661 : 1))

    That means that the 0'th point may be replaced by the displayed
    point to achieve an index gain of 11::

        sage: p_saturation([res[2],P-6*Q,R],11)
        (True, {})

    TESTS:

    See :trac:`27387`::

        sage: K.<a> = NumberField(x^2-x-26)
        sage: E = EllipticCurve([a,1-a,0,93-16*a, 3150-560*a])
        sage: P = E([65-35*a/3, (959*a-5377)/9])
        sage: T = E.torsion_points()[0]
        sage: from sage.schemes.elliptic_curves.saturation import p_saturation
        sage: p_saturation([P,T],2,True, {}, True)
        Using sieve method to saturate...
        ...
        -- points were not 2-saturated, gaining index 2
        (False, 0, (-1/4*a + 3/4 : 59/8*a - 317/8 : 1))
        sage: p_saturation([P,T],2,False, {})
        (False, 0, (-1/4*a + 3/4 : 59/8*a - 317/8 : 1))

    """
    # This code does a lot of residue field construction and elliptic curve
    # group structure computations, and would benefit immensely if those
    # were sped up.
    n = len(Plist)
    if n == 0:
        return (True, lin_combs)

    if n == 1:
        try:
            return (False, 0, Plist[0].division_points(p)[0])
        except IndexError:
            return (True, lin_combs)

    E = Plist[0].curve()

    if not sieve:
        from sage.groups.generic import multiples
        from sage.schemes.projective.projective_space import ProjectiveSpace

        mults = [list(multiples(P, p))
                 for P in Plist[:-1]] + [list(multiples(Plist[-1], 2))]
        E0 = E(0)

        for v in ProjectiveSpace(GF(p), n - 1):  # an iterator
            w = tuple(int(x) for x in v)
            try:
                P = lin_combs[w]
            except KeyError:
                P = sum([m[c] for m, c in zip(mults, w)], E0)
                lin_combs[w] = P
            divisors = P.division_points(p)
            if divisors:
                if verbose:
                    print("  points not saturated at %s, increasing index" % p)
                # w will certainly have a coordinate equal to 1
                return (False, w.index(1), divisors[0])
        # we only get here if no linear combination is divisible by p,
        # so the points are p-saturated:
        return (True, lin_combs)

    # Now we use the more sophisticated sieve to either prove
    # p-saturation, or compute a much smaller list of possible points
    # to test for p-divisibility:

    E = Plist[0].curve()
    K = E.base_ring()

    def projections(Q, p):
        r"""
        Project points onto (E mod Q)(K mod Q) \otimes \F_p.

        Returns a list of 0, 1 or 2 vectors in \F_p^n
        """
        # NB we do not do E.reduction(Q) since that may change the
        # model which will cause problems when we reduce points.
        # see trac #27387
        Ek = E.change_ring(K.residue_field(Q))
        G = Ek.abelian_group()  # cached!

        if not p.divides(G.order()):
            return []

        if verbose:
            print("There is %s-torsion modulo %s, projecting points" % (p, Q))

        def proj1(pt):
            try:
                return Ek(pt)
            except ZeroDivisionError:
                return Ek(0)

        projPlist = [proj1(pt) for pt in Plist]

        if verbose:
            print(" --> %s" % projPlist)

        gens = G.gens()
        orders = G.generator_orders()
        from sage.groups.generic import discrete_log_lambda
        from sage.modules.all import vector

        if len(gens) == 1:  # cyclic case
            n = orders[0]
            m = n.prime_to_m_part(p)  # prime-to-p part of order
            p2 = n // m  # p-primary order
            g = (m * gens[0]).element()  # generator of p-primary part
            assert g.order() == p2
            # if verbose:
            #     print("   cyclic, %s-primary part generated by %s of order %s" % (p,g,p2))

            # multiplying any point by m takes it into the p-primary
            # part -- this way we do discrete logs in a cyclic group
            # of order p2 (which will often just be p) and not order n

            v = [
                discrete_log_lambda(m * pt, g, (0, p2), '+')
                for pt in projPlist
            ]
            # entries of v are well-defined mod p2, hence mod p
            return [vector(GF(p), v)]

        # Now the reduction is not cyclic, but we still handle
        # separately the case where the p-primary part is cyclic,
        # where a similar discrete log computation suffices; in the
        # remaining case (p-rank 2) we use the Weil pairing instead.

        # Note the code makes no assumption about which generator
        # order divides the other, since conventions differ!

        mm = [ni.prime_to_m_part(p) for ni in orders]
        pp = [ni // mi for ni, mi in zip(orders, mm)]  # p-powers
        gg = [(mi * gi).element()
              for mi, gi in zip(mm, gens)]  # p-power order gens
        m = max(mm)  # = lcm(mm), the prime-to-p exponent of G;
        # multiply by this to map onto the p-primary part.
        p2 = max(pp)  # p-exponent of G
        p1 = min(pp)  # == 1 iff p-primary part is cyclic

        if p1 == 1:  # p-primary part is cyclic of order p2
            g = gg[pp.index(p2)]  # g generates the p-primary part
            # and we do discrete logs there:

            assert g.order() == p2
            # if verbose:
            #     print("   non-cyclic but %s-primary part cyclic generated by %s of order %s" % (p,g,p2))
            v = [
                discrete_log_lambda(m * pt, g, (0, p2), '+')
                for pt in projPlist
            ]
            # entries of v are well-defined mod p2, hence mod p
            return [vector(GF(p), v)]

        # Now the p-primary part is non-cyclic of exponent p2; we use
        # Weil pairings of this order, whose values are p1'th roots of unity.

        # if verbose:
        #     print("   %s-primary part non-cyclic generated by %s of orders %s" % (p,gg,pp))
        zeta = gg[0].weil_pairing(gg[1], p2)  # a primitive p1'th root of unity
        if zeta.multiplicative_order() != p1:
            if verbose:
                print("Ek = ", Ek)
                print("(p1,p2) = (%s,%s)" % (p1, p2))
                print("gg = (%s,%s)" % (gg[0], gg[1]))
            raise RuntimeError("Weil pairing error during saturation.")

        # these are the homomorphisms from E to F_p (for g in gg):
        def a(pt, g):
            """Return the zeta-based log of the Weil pairing of ``m*pt`` with ``g``.
            """
            w = (m * pt).weil_pairing(g, p2)  # result is a p1'th root of unity
            return discrete_log_lambda(w, zeta, (0, p1), '*')

        return [vector(GF(p), [a(pt, gen) for pt in projPlist]) for gen in gg]

    if verbose:
        print("Using sieve method to saturate...")
    from sage.matrix.all import matrix
    A = matrix(GF(p), 0, n)
    rankA = 0
    count = 0
    from sage.sets.primes import Primes
    Edisc = E.discriminant()
    for q in Primes():
        for Q in K.primes_above(q, degree=1):
            if not E.is_local_integral_model(Q) or Edisc.valuation(Q) != 0:
                continue
            vecs = projections(Q, p)
            if len(vecs) == 0:
                continue
            for v in vecs:
                A = matrix(A.rows() + [v])
            newrank = A.rank()
            if verbose:
                print(" --rank is now %s" % newrank)
            if newrank == n:
                if verbose:
                    print("Reached full rank: points were %s-saturated" % p)
                return (True, lin_combs)
            if newrank == rankA:
                count += 1
                if count == 10:
                    if verbose:
                        print("! rank same for 10 steps, checking kernel...")
                    # no increase in rank for the last 10 primes Q
                    # find the points in the kernel and call the no-sieve version
                    vecs = A.right_kernel().basis()
                    if verbose:
                        print("kernel vectors: %s" % vecs)
                    Rlist = [
                        sum([int(vi) * Pi for vi, Pi in zip(v, Plist)], E(0))
                        for v in vecs
                    ]
                    if verbose:
                        print("points generating kernel: %s" % Rlist)

                    res = p_saturation(Rlist,
                                       p,
                                       sieve=False,
                                       lin_combs=dict(),
                                       verbose=verbose)
                    if res[0]:  # points really were saturated
                        if verbose:
                            print("-- points were %s-saturated" % p)
                        return (True, lin_combs)
                    else:  # they were not, and Rlist[res[1]] can be
                        # replaced by res[2] to enlarge the span; we
                        # need to find a point in Plist which can be
                        # replaced: any one with a nonzero coordinate in
                        # the appropriate kernel vector will do.
                        if verbose:
                            print(
                                "-- points were not %s-saturated, gaining index %s"
                                % (p, p))
                        j = next(i for i, x in enumerate(vecs[res[1]]) if x)
                        return (False, j, res[2])
                else:  # rank stayed same; carry on using more Qs
                    pass
            else:  # rank went up but is <n; carry on using more Qs
                rankA = newrank
                count = 0
    # We reach here only if using all good primes of norm<1000 the
    # rank never stuck for 10 consecutive Qs but is still < n.  That
    # means that n is rather large, or perhaps that E has a large
    # number of bad primes.  So we fall back to the naive method,
    # still making use of any partial information about the kernel we
    # have acquired.

    vecs = A.right_kernel().basis()
    Rlist = [
        sum([int(vi) * Pi for vi, Pi in zip(v, Plist)], E(0)) for v in vecs
    ]
    res = p_saturation(Rlist,
                       p,
                       sieve=False,
                       lin_combs=dict(),
                       verbose=verbose)
    if res[0]:  # points really were saturated
        return (True, lin_combs)
    else:
        j = next(i for i, x in enumerate(vecs[res[1]]) if x)
        return (False, j, res[2])
Beispiel #7
0
    def p_saturation(self, Plist, p, sieve=True):
        r"""Checks whether the list of points is `p`-saturated.

        INPUT:

        - ``Plist`` (list) - a list of independent points on one elliptic curve.

        - ``p`` (integer) - a prime number.

        - ``sieve`` (boolean) - if True, use a sieve (when there are at
          least 2 points); otherwise test all combinations.

        .. note::

            The sieve is much more efficient when the points are
            saturated and the number of points or the prime are large.

        OUTPUT:

        Either ``False`` if the points are `p`-saturated, or (``i``,
        ``newP``) if they are not `p`-saturated, in which case after
        replacing the i'th point with ``newP``, the subgroup generated
        contains that generated by ``Plist`` with index `p`.

        EXAMPLES::

            sage: from sage.schemes.elliptic_curves.saturation import EllipticCurveSaturator
            sage: E = EllipticCurve('389a')
            sage: K.<i> = QuadraticField(-1)
            sage: EK = E.change_ring(K)
            sage: P = EK(1+i,-1-2*i)
            sage: saturator = EllipticCurveSaturator(EK)
            sage: saturator.p_saturation([P],2)
            False
            sage: saturator.p_saturation([2*P],2)
            (0, (i + 1 : -2*i - 1 : 1))

            sage: Q = EK(0,0)
            sage: R = EK(-1,1)
            sage: saturator.p_saturation([P,Q,R],3)
            False

        Here we see an example where 19-saturation is proved, with the
        verbose flag set to True so that we can see what is going on::

            sage: saturator = EllipticCurveSaturator(EK, verbose=True)
            sage: saturator.p_saturation([P,Q,R],19)
            Using sieve method to saturate...
            E has 19-torsion over Finite Field of size 197, projecting points
            --> [(15 : 168 : 1), (0 : 0 : 1), (196 : 1 : 1)]
            --rank is now 1
            E has 19-torsion over Finite Field of size 197, projecting points
            --> [(184 : 27 : 1), (0 : 0 : 1), (196 : 1 : 1)]
            --rank is now 2
            E has 19-torsion over Finite Field of size 293, projecting points
            --> [(139 : 16 : 1), (0 : 0 : 1), (292 : 1 : 1)]
             --rank is now 3
            Reached full rank: points were 19-saturated
            False

        An example where the points are not 11-saturated::

            sage: saturator = EllipticCurveSaturator(EK, verbose=False)
            sage: res = saturator.p_saturation([P+5*Q,P-6*Q,R],11); res
            (0,
            (-5783311/14600041*i + 1396143/14600041 : 37679338314/55786756661*i + 3813624227/55786756661 : 1))

        That means that the 0'th point may be replaced by the displayed
        point to achieve an index gain of 11::

            sage: saturator.p_saturation([res[1],P-6*Q,R],11)
            False

        TESTS:

        See :trac:`27387`::

            sage: K.<a> = NumberField(x^2-x-26)
            sage: E = EllipticCurve([a,1-a,0,93-16*a, 3150-560*a])
            sage: P = E([65-35*a/3, (959*a-5377)/9])
            sage: T = E.torsion_points()[0]
            sage: from sage.schemes.elliptic_curves.saturation import EllipticCurveSaturator
            sage: saturator = EllipticCurveSaturator(E, True)
            sage: saturator.p_saturation([P,T], 2)
            Using sieve method to saturate...
            ...
            -- points were not 2-saturated, gaining index 2
            (0, (-1/4*a + 3/4 : 59/8*a - 317/8 : 1))

        A CM example where large siecing primes are needed (LMFDB
        label 2.0.3.1-50625.1-CMb2)::

            sage: K.<a> = NumberField(x^2-x+1)
            sage: E = EllipticCurve(K, [0, 0, 1, -750, 7906])
            sage: E.has_rational_cm()
            True
            sage: E.cm_discriminant()
            -27
            sage: points = [E([10, -38]), E([15/49*a + 760/49, 675/343*a - 884/343])]
            sage: E.saturation(points, verbose=True) # long time (17s)
            Computing lower height bound..
            ..done: 7.168735020029907e-06
            p-saturating for primes p < 132
            ...
            Saturating at p=131
            --starting full 131-saturation
            Using sieve method to saturate...
            E has 131-torsion over Finite Field of size 617011, projecting points
            --> [(10 : 616973 : 1), (592083 : 192224 : 1)]
            --rank is now 1
            --rank is now 2
            Reached full rank: points were 131-saturated
            Points were 131-saturated
            --already 131-saturated
            ([(10 : -38 : 1), (15/49*a + 760/49 : 675/343*a - 884/343 : 1)],
            1,
            0.123378097374749)

        """
        verbose = self._verbose
        # This code does a lot of elliptic curve group structure
        # computations, and would benefit immensely if those were sped
        # up.
        n = len(Plist)
        if n == 0:
            return False

        if n == 1 and p == 2:
            pts = Plist[0].division_points(p)
            if pts:
                return (0, pts[0])
            else:
                return False

        E = self._curve

        if not sieve:
            from sage.groups.generic import multiples
            from sage.schemes.projective.projective_space import ProjectiveSpace

            mults = [list(multiples(P, p))
                     for P in Plist[:-1]] + [list(multiples(Plist[-1], 2))]
            E0 = E(0)

            for v in ProjectiveSpace(GF(p), n - 1):  # an iterator
                w = tuple(int(x) for x in v)
                P = sum([m[c] for m, c in zip(mults, w)], E0)
                pts = P.division_points(p)
                if pts:
                    if verbose:
                        print(
                            "  points not saturated at {}, increasing index by {}"
                            .format(p, p))
                        # w will certainly have a coordinate equal to 1
                    return (w.index(1), pts[0])
            # we only get here if no linear combination is divisible by p,
            # so the points are p-saturated:
            return False

        # Now we use the more sophisticated sieve to either prove
        # p-saturation, or compute a much smaller list of possible points
        # to test for p-divisibility:

        if verbose:
            print("Using sieve method to saturate...")
        from sage.matrix.all import matrix
        from sage.sets.primes import Primes

        A = matrix(GF(p), 0, n)
        rankA = 0
        count = 0

        # We reduce the curve modulo primes Q of degree 1 above
        # rational primes q chosen as follows: we avoid primes of bad
        # reduction and primes dividing the discriminant of the
        # defining polynomial of the field, so that we can avoid
        # constructing number field primes entirely and just look for
        # roots amodq of the defining polynomial mod q, then reduction
        # of the curve is easy.

        # We also avoid primes dividing the denominators of any of the
        # points: the points would reduce to 0 modulo such primes
        # anyway, and this way reduction of the points is also easy.

        # Lastly, there is a special case when E has rational CM by
        # some discriminant D.  In the non-split case (kro(D,p)=-1)
        # the image of the mod-p Galois representation is cyclic of
        # order p^2-1 and there will be no p-torsion mod Q unless
        # Frob(Q) has trivial image; a necessary condition for this is
        # q=1 (mod p) so we restrict to such q.  That way the density
        # of q for which there is a point of order p is 1/(p+1)
        # instead of 1/(p^2-1).  This test was put in after running
        # many tests: for example, LMFDB curve 2.0.3.1-50625.1-CMb2
        # has saturation index bound 132 and to saturate at p=131
        # requires q=617011. (In the split case the density is 1/(p-1)
        # and there is no simple test.)

        avoid = [self._N, self._D
                 ] + [P[0].denominator_ideal().norm() for P in Plist]
        cm_test = E.has_rational_cm() and kro(E.cm_discriminant(), p) == -1
        for q in Primes():
            if any(q.divides(m) for m in avoid):
                continue
            if cm_test and not p.divides(q - 1):
                continue
            self.add_reductions(q)  # does nothing if key q is already there
            for amodq in self._reductions[q]:
                (nq, Eq) = self._reductions[q][amodq]
                if not p.divides(nq):
                    continue
                if verbose:
                    print("E has %s-torsion over %s, projecting points" %
                          (p, GF(q)))
                projPlist = [
                    Eq([reduce_mod_q(c, amodq) for c in pt]) for pt in Plist
                ]
                if verbose:
                    print(" --> %s" % projPlist)
                try:
                    vecs = p_projections(Eq, projPlist, p)
                except ValueError:
                    vecs = []
                for v in vecs:
                    A = matrix(A.rows() + [v])
                    newrank = A.rank()
                    if verbose:
                        print(" --rank is now %s" % newrank)
                    if newrank == n:
                        if verbose:
                            print(
                                "Reached full rank: points were %s-saturated" %
                                p)
                        return False
                    if newrank == rankA:
                        count += 1
                        if count == 15:
                            if verbose:
                                print(
                                    "! rank same for 15 steps, checking kernel..."
                                )
                            # no increase in rank for the last 15 primes Q
                            # find the points in the kernel and call the no-sieve version
                            vecs = A.right_kernel().basis()
                            if verbose:
                                print("kernel vectors: %s" % vecs)
                            Rlist = [
                                sum([int(vi) * Pi for vi, Pi in zip(v, Plist)],
                                    E(0)) for v in vecs
                            ]
                            if verbose:
                                print("points generating kernel: %s" % Rlist)

                            # If the nullity of A were 1 (the usual
                            # case) we take any nonzero vector in its
                            # kernel and compute that linear
                            # combination of the points, giving a
                            # point which is certainly a p-multiple
                            # modulo 15 primes Q, and we test if it
                            # actually is a p-multiple:
                            if len(Rlist) == 1:
                                R = Rlist[0]
                                pts = R.division_points(p)
                                if pts:
                                    pt = pts[0]
                                    v = vecs[0]
                                    # replace one of the original
                                    # points with this one; we can
                                    # replace any for which the
                                    # coefficient of v is nonzero
                                    if verbose:
                                        print(
                                            "-- points were not {}-saturated, gaining index {}"
                                            .format(p, p))
                                    j = next(i for i, x in enumerate(v) if x)
                                    return (j, pt)
                                else:  # R is not a p-multiple so the
                                    # points were p-saturated
                                    return False

                            # Else we call the non-sieve version with
                            # a list of points which are all
                            # p-multiples modulo 15 primes, and we
                            # will just try to divide all linear
                            # combinations of them
                            res = self.p_saturation(Rlist, p, sieve=False)
                            if res:
                                jj, R = res
                                v = vecs[jj]
                                # The v-linear combination of Rlist
                                # really is p*R.  Now to enlarge the
                                # span, we may replce the j'th point
                                # in Plist with R, where v[j] is
                                # non-zero.
                                if verbose:
                                    print(
                                        "-- points were not {}-saturated, gaining index {}"
                                        .format(p, p))
                                j = next(i for i, x in enumerate(v) if x)
                                return (j, R)
                            else:
                                # points really were saturated
                                if verbose:
                                    print("-- points were %s-saturated" % p)
                                return False
                    else:  # rank went up but is <n; carry on using more Qs
                        rankA = newrank
                        count = 0