Example #1
0
def is_Q_curve(E, maxp=100, certificate=False, verbose=False):
    r"""
    Return whether ``E`` is a `\QQ`-curve, with optional certificate.

    INPUT:

    - ``E`` (elliptic curve) -- an elliptic curve over a number field.

    - ``maxp`` (int, default 100): bound on primes used for checking
      necessary local conditions.  The result will not depend on this,
      but using a larger value may return ``False`` faster.

    - ``certificate`` (bool, default ``False``): if ``True`` then a
      second value is returned giving a certificate for the
      `\QQ`-curve property.

    OUTPUT:

    If ``certificate`` is ``False``: either ``True`` (if `E` is a
    `\QQ`-curve), or ``False``.

    If ``certificate`` is ``True``: a tuple consisting of a boolean
    flag as before and a certificate, defined as follows:

    - when the flag is ``True``, so `E` is a `\QQ`-curve:

        - either {'CM':`D`} where `D` is a negative discriminant, when
          `E` has potential CM with discriminant `D`;

        - otherwise {'CM': `0`, 'core_poly': `f`, 'rho': `\rho`, 'r':
          `r`, 'N': `N`}, when `E` is a non-CM `\QQ`-curve, where the
          core polynomial `f` is an irreducible monic polynomial over
          `QQ` of degree `2^\rho`, all of whose roots are
          `j`-invariants of curves isogenous to `E`, the core level
          `N` is a square-free integer with `r` prime factors which is
          the LCM of the degrees of the isogenies between these
          conjugates.  For example, if there exists a curve `E'`
          isogenous to `E` with `j(E')=j\in\QQ`, then the certificate
          is {'CM':0, 'r':0, 'rho':0, 'core_poly': x-j, 'N':1}.

    - when the flag is ``False``, so `E` is not a `\QQ`-curve, the
      certificate is a prime `p` such that the reductions of `E` at
      the primes dividing `p` are inconsistent with the property of
      being a `\QQ`-curve.  See the ALGORITHM section for details.

    ALGORITHM:

    See [CrNa2020]_ for details.

    1. If `E` has rational `j`-invariant, or has CM, then return
    ``True``.

    2. Replace `E` by a curve defined over `K=\QQ(j(E))`. Let `N` be
    the conductor norm.

    3. For all primes `p\mid N` check that the valuations of `j` at
    all `P\mid p` are either all negative or all non-negative; if not,
    return ``False``.

    4. For `p\le maxp`, `p\not\mid N`, check that either `E` is
    ordinary mod `P` for all `P\mid p`, or `E` is supersingular mod
    `P` for all `P\mid p`; if neither, return ``False``.  If all are
    ordinary, check that the integers `a_P(E)^2-4N(P)` have the same
    square-free part; if not, return ``False``.

    5. Compute the `K`-isogeny class of `E` using the "heuristic"
    option (which is faster, but not guaranteed to be complete).
    Check whether the set of `j`-invariants of curves in the class of
    `2`-power degree contains a complete Galois orbit.  If so, return
    ``True``.

    6. Otherwise repeat step 4 for more primes, and if still
    undecided, repeat Step 5 without the "heuristic" option, to get
    the complete `K`-isogeny class (which will probably be no bigger
    than before).  Now return ``True`` if the set of `j`-invariants of
    curves in the class contains a complete Galois orbit, otherwise
    return ``False``.

    EXAMPLES:

    A non-CM curve over `\QQ` and a CM curve over `\QQ` are both
    trivially `\QQ`-curves::

        sage: from sage.schemes.elliptic_curves.Qcurves import is_Q_curve
        sage: E = EllipticCurve([1,2,3,4,5])
        sage: flag, cert = is_Q_curve(E, certificate=True)
        sage: flag
        True
        sage: cert
        {'CM': 0, 'N': 1, 'core_poly': x, 'r': 0, 'rho': 0}

        sage: E = EllipticCurve(j=8000)
        sage: flag, cert = is_Q_curve(E, certificate=True)
        sage: flag
        True
        sage: cert
        {'CM': -8}

    A non-`\QQ`-curve over a quartic field.  The local data at bad
    primes above `3` is inconsistent::

        sage: from sage.schemes.elliptic_curves.Qcurves import is_Q_curve
        sage: R.<x> = PolynomialRing(QQ)
        sage: K.<a> = NumberField(R([3, 0, -5, 0, 1]))
        sage: E = EllipticCurve([K([-3,-4,1,1]),K([4,-1,-1,0]),K([-2,0,1,0]),K([-621,778,138,-178]),K([9509,2046,-24728,10380])])
        sage: is_Q_curve(E, certificate=True, verbose=True)
        Checking whether Elliptic Curve defined by y^2 + (a^3+a^2-4*a-3)*x*y + (a^2-2)*y = x^3 + (-a^2-a+4)*x^2 + (-178*a^3+138*a^2+778*a-621)*x + (10380*a^3-24728*a^2+2046*a+9509) over Number Field in a with defining polynomial x^4 - 5*x^2 + 3 is a Q-curve
        No: inconsistency at the 2 primes dividing 3
        - potentially multiplicative: [True, False]
        (False, 3)

    A non-`\QQ`-curve over a quadratic field.  The local data at bad
    primes is consistent, but the local test at good primes above `13`
    is not::

        sage: K.<a> = NumberField(R([-10, 0, 1]))
        sage: E = EllipticCurve([K([0,1]),K([-1,-1]),K([0,0]),K([-236,40]),K([-1840,464])])
        sage: is_Q_curve(E, certificate=True, verbose=True)
        Checking whether Elliptic Curve defined by y^2 + a*x*y = x^3 + (-a-1)*x^2 + (40*a-236)*x + (464*a-1840) over Number Field in a with defining polynomial x^2 - 10 is a Q-curve
        Applying local tests at good primes above p<=100
        No: inconsistency at the 2 ordinary primes dividing 13
        - Frobenius discriminants mod squares: [-1, -3]
        No: local test at p=13 failed
        (False, 13)

    A quadratic `\QQ`-curve with CM discriminant `-15` (`j`-invariant not in `\QQ`)::

        sage: from sage.schemes.elliptic_curves.Qcurves import is_Q_curve
        sage: R.<x> = PolynomialRing(QQ)
        sage: K.<a> = NumberField(R([-1, -1, 1]))
        sage: E = EllipticCurve([K([1,0]),K([-1,0]),K([0,1]),K([0,-2]),K([0,1])])
        sage: is_Q_curve(E, certificate=True, verbose=True)
        Checking whether Elliptic Curve defined by y^2 + x*y + a*y = x^3 + (-1)*x^2 + (-2*a)*x + a over Number Field in a with defining polynomial x^2 - x - 1 is a Q-curve
        Yes: E is CM (discriminant -15)
        (True, {'CM': -15})

    An example over `\QQ(\sqrt{2},\sqrt{3})`.  The `j`-invariant is in
    `\QQ(\sqrt{6})`, so computations will be done over that field, and
    in fact there is an isogenous curve with rational `j`, so we have
    a so-called rational `\QQ`-curve::

        sage: K.<a> = NumberField(R([1, 0, -4, 0, 1]))
        sage: E = EllipticCurve([K([-2,-4,1,1]),K([0,1,0,0]),K([0,1,0,0]),K([-4780,9170,1265,-2463]),K([163923,-316598,-43876,84852])])
        sage: flag, cert = is_Q_curve(E, certificate=True)
        sage: flag
        True
        sage: cert
        {'CM': 0, 'N': 1, 'core_degs': [1], 'core_poly': x - 85184/3, 'r': 0, 'rho': 0}

    Over the same field, a so-called strict `\QQ`-curve which is not
    isogenous to one with rational `j`, but whose core field is
    quadratic. In fact the isogeny class over `K` consists of `6`
    curves, four with conjugate quartic `j`-invariants and `2` with
    quadratic conjugate `j`-invariants in `\QQ(\sqrt{3})` (but which
    are not base-changes from the quadratic subfield)::

        sage: E = EllipticCurve([K([0,-3,0,1]),K([1,4,0,-1]),K([0,0,0,0]),K([-2,-16,0,4]),K([-19,-32,4,8])])
        sage: flag, cert = is_Q_curve(E, certificate=True)
        sage: flag
        True
        sage: cert
        {'CM': 0,
        'N': 2,
        'core_degs': [1, 2],
        'core_poly': x^2 - 840064*x + 1593413632,
        'r': 1,
        'rho': 1}

    """
    from sage.rings.number_field.number_field_base import is_NumberField

    if verbose:
        print("Checking whether {} is a Q-curve".format(E))

    try:
        assert is_NumberField(E.base_field())
    except (AttributeError, AssertionError):
        raise TypeError(
            "{} must be an elliptic curve defined over a number field in is_Q_curve()"
        )

    from sage.rings.integer_ring import ZZ
    from sage.arith.functions import lcm
    from sage.libs.pari import pari
    from sage.rings.number_field.number_field import NumberField
    from sage.schemes.elliptic_curves.constructor import EllipticCurve
    from sage.schemes.elliptic_curves.cm import cm_j_invariants_and_orders, is_cm_j_invariant

    # Step 1

    # all curves with rational j-invariant are Q-curves:
    jE = E.j_invariant()
    if jE in QQ:
        if verbose:
            print("Yes: j(E) is in QQ")
        if certificate:
            # test for CM
            for d, f, j in cm_j_invariants_and_orders(QQ):
                if jE == j:
                    return True, {'CM': d * f**2}
            # else not CM
            return True, {
                'CM': ZZ(0),
                'r': ZZ(0),
                'rho': ZZ(0),
                'N': ZZ(1),
                'core_poly': polygen(QQ)
            }
        else:
            return True

    # CM curves are Q-curves:
    flag, df = is_cm_j_invariant(jE)
    if flag:
        d, f = df
        D = d * f**2
        if verbose:
            print("Yes: E is CM (discriminant {})".format(D))
        if certificate:
            return True, {'CM': D}
        else:
            return True

    # Step 2: replace E by a curve defined over Q(j(E)):

    K = E.base_field()
    jpoly = jE.minpoly()
    if jpoly.degree() < K.degree():
        if verbose:
            print("switching to smaller base field: j's minpoly is {}".format(
                jpoly))
        f = pari(jpoly).polredbest().sage({'x': jpoly.parent().gen()})
        K2 = NumberField(f, 'b')
        jE = jpoly.roots(K2)[0][0]
        if verbose:
            print("New j is {} over {}, with minpoly {}".format(
                jE, K2, jE.minpoly()))
        #assert jE.minpoly()==jpoly
        E = EllipticCurve(j=jE)
        K = K2
        if verbose:
            print("New test curve is {}".format(E))

    # Step 3: check primes of bad reduction

    NN = E.conductor().norm()
    for p in NN.support():
        Plist = K.primes_above(p)
        if len(Plist) < 2:
            continue
        # pot_mult = potential multiplicative reduction
        pot_mult = [jE.valuation(P) < 0 for P in Plist]
        consistent = all(pot_mult) or not any(pot_mult)
        if not consistent:
            if verbose:
                print("No: inconsistency at the {} primes dividing {}".format(
                    len(Plist), p))
                print("  - potentially multiplicative: {}".format(pot_mult))
            if certificate:
                return False, p
            else:
                return False

    # Step 4 check: primes P of good reduction above p<=B:

    if verbose:
        print("Applying local tests at good primes above p<={}".format(maxp))

    res4, p = Step4Test(E, B=maxp, oldB=0, verbose=verbose)
    if not res4:
        if verbose:
            print("No: local test at p={} failed".format(p))
        if certificate:
            return False, p
        else:
            return False

    if verbose:
        print("...all local tests pass for p<={}".format(maxp))

    # Step 5: compute the (partial) K-isogeny class of E and test the
    # set of j-invariants in the class:

    C = E.isogeny_class(algorithm='heuristic', minimal_models=False)
    jC = [E2.j_invariant() for E2 in C]
    centrejpols = conjugacy_test(jC, verbose=verbose)
    if centrejpols:
        if verbose:
            print(
                "Yes: the isogeny class contains a complete conjugacy class of j-invariants"
            )
        if certificate:
            for f in centrejpols:
                rho = f.degree().valuation(2)
                centre_indices = [i for i, j in enumerate(jC) if f(j) == 0]
                M = C.matrix()
                core_degs = [M[centre_indices[0], i] for i in centre_indices]
                level = lcm(core_degs)
                if level.is_squarefree():
                    r = len(level.prime_divisors())
                    cert = {
                        'CM': ZZ(0),
                        'core_poly': f,
                        'rho': rho,
                        'r': r,
                        'N': level,
                        'core_degs': core_degs
                    }
                    return True, cert
            print("No central curve found")
        else:
            return True

    # Now we are undecided.  This can happen if either (1) E is not a
    # Q-curve but we did not use enough primes in Step 4 to detect
    # this, or (2) E is a Q-curve but in Step 5 we did not compute the
    # complete isogeny class.  Case (2) is most unlikely since the
    # heuristic bound used in computing isogeny classes means that we
    # have all isogenous curves linked to E by an isogeny of degree
    # supported on primes<1000.

    # We first rerun Step 4 with a larger bound.

    xmaxp = 10 * maxp
    if verbose:
        print(
            "Undecided after first round, so we apply more local tests, up to {}"
            .format(xmaxp))

    res4, p = Step4Test(E, B=xmaxp, oldB=maxp, verbose=verbose)
    if not res4:
        if verbose:
            print("No: local test at p={} failed".format(p))
        if certificate:
            return False, p
        else:
            return False

    # Now we rerun Step 5 using a rigorous computation of the complete
    # isogeny class.  This will probably contain no more curves than
    # before, in which case -- since we already tested that the set of
    # j-invariants does not contain a complete Galois conjugacy class
    # -- we can deduce that E is not a Q-curve.

    if verbose:
        print("...all local tests pass for p<={}".format(xmaxp))
        print("We now compute the complete isogeny class...")

    Cfull = E.isogeny_class(minimal_models=False)
    jCfull = [E2.j_invariant() for E2 in Cfull]

    if len(jC) == len(jCfull):
        if verbose:
            print("...and find that we already had the complete class:so No")
        if certificate:
            return False, 0
        else:
            return False
    if verbose:
        print(
            "...and find that the class contains {} curves, not just the {} we computed originally"
            .format(len(jCfull), len(jC)))
    centrejpols = conjugacy_test(jCfull, verbose=verbose)
    if cert:
        if verbose:
            print(
                "Yes: the isogeny class contains a complete conjugacy class of j-invariants"
            )
        if certificate:
            return True, centrejpols
        else:
            return True
    if verbose:
        print(
            "No: the isogeny class does *not* contain a complete conjugacy class of j-invariants"
        )
    if certificate:
        return False, 0
    else:
        return False