generate.py

back to table · edit · history · where entries came from · files · download

6457 bytes, as of the version from 2026-09-22 11:54 (current). Recorded here, not run.

"""L-invariants of elliptic curves over Q with split multiplicative reduction -- numberdb.org/T410.

Run it with SageMath:

    $ sage -pip install numberdb          # once
    $ sage -python generate.py            # check the table against this code
    $ sage -python generate.py --publish  # send it, with NUMBERDB_API_KEY set

The file reads elliptic curves from Sage's mini Cremona database with conductor
N <= 100. For each prime p of split multiplicative reduction it stores the
Mazur-Tate-Teitelbaum L-invariant, using Sage's Tate curve implementation and
the Iwasawa branch of the p-adic logarithm, log_p(p) = 0. The p-adic precision
is the least n with p^n >= 10^50.
"""

import os
import re
import sys

import numberdb.sage as numberdb
from sage.databases.cremona import CremonaDatabase, cremona_to_lmfdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.schemes.elliptic_curves.constructor import EllipticCurve


TABLE = os.environ.get("NUMBERDB_TABLE", "T410")
CONDUCTOR_BOUND = 100
DECIMAL_DIGITS = 50
PRECISION_TARGET = ZZ(10) ** DECIMAL_DIGITS
LOG_GUARD = 10
COMPUTE_GUARD = 10
AGREEMENT_GUARD = 20

_RECORDS = None
_BY_KEY = None


def key_from_stdin():
    if os.environ.get("NUMBERDB_KEY_FROM_STDIN") != "1":
        return
    token = sys.stdin.read().strip()
    if "=" in token and token.split("=", 1)[0].isupper():
        token = token.split("=", 1)[1].strip().strip("'\"")
    if token:
        os.environ["NUMBERDB_API_KEY"] = token


def precision_for_prime(p, decimal_digits=DECIMAL_DIGITS):
    """Least n with p^n carrying at least decimal_digits decimal digits."""
    p = ZZ(p)
    n = 1
    power = p
    target = ZZ(10) ** ZZ(decimal_digits)
    while power < target:
        n += 1
        power *= p
    return n


def label_key(label):
    found = re.fullmatch(r"([a-z]+)([0-9]+)", label)
    if not found:
        return (label, 0)
    curve_class, number = found.groups()
    return (len(curve_class), curve_class, int(number))


def curve_from_ainvs(ainvs):
    """Build an elliptic curve without Sage's Sequence constructor path."""
    return EllipticCurve(QQ, [QQ(a) for a in ainvs])


def cremona_rows(bound):
    database = CremonaDatabase()
    for conductor in range(11, bound + 1):
        for short_label, data in sorted(
                database.allcurves(conductor).items(),
                key=lambda item: label_key(item[0])):
            label = "%s%s" % (conductor, short_label)
            yield conductor, label, tuple(QQ(a) for a in data[0])


def record_key(params):
    return tuple(str(params[name]) for name in ("N", "c4", "c6", "p"))


def independent_l_invariant(tate_curve, p, precision):
    q = tate_curve.parameter(precision + LOG_GUARD)
    return (q.log(p_branch=0) / ZZ(q.valuation())).add_bigoh(precision)


def entry_comment(cremona_label, lmfdb_label):
    if lmfdb_label:
        return "Cremona label %s; LMFDB label %s." % (
            cremona_label, lmfdb_label)
    return "Cremona label %s." % (cremona_label,)


def records():
    global _RECORDS, _BY_KEY
    if _RECORDS is not None:
        return _RECORDS

    out = []
    seen = set()
    for conductor, cremona_label, ainvs in cremona_rows(CONDUCTOR_BOUND):
        curve = curve_from_ainvs(ainvs)
        c4 = ZZ(curve.c4())
        c6 = ZZ(curve.c6())
        try:
            lmfdb_label = cremona_to_lmfdb(cremona_label)
        except Exception:  # noqa: BLE001
            lmfdb_label = None
        for p in sorted(ZZ(conductor).prime_divisors()):
            p = ZZ(p)
            if not curve.has_split_multiplicative_reduction(p):
                continue
            precision = precision_for_prime(p)
            tate_curve = curve.tate_curve(p)
            value = tate_curve.L_invariant(
                precision + COMPUTE_GUARD).add_bigoh(precision)
            repeated = tate_curve.L_invariant(
                precision + AGREEMENT_GUARD).add_bigoh(precision)
            if value != repeated:
                raise ArithmeticError(
                    "%s at p=%s: computations at two precisions disagree: "
                    "%s and %s" % (cremona_label, p, value, repeated))
            expected = independent_l_invariant(tate_curve, p, precision)
            if value != expected:
                raise ArithmeticError(
                    "%s at p=%s: L_invariant %s, log(q)/ord(q) %s"
                    % (cremona_label, p, value, expected))
            if value.precision_absolute() < precision:
                raise ArithmeticError(
                    "%s at p=%s has precision %s, expected at least %s"
                    % (cremona_label, p, value.precision_absolute(), precision))
            params = {
                "N": str(conductor),
                "c4": str(c4),
                "c6": str(c6),
                "p": str(p),
            }
            key = record_key(params)
            if key in seen:
                raise ArithmeticError(
                    "the parameters do not identify one row: %s"
                    % (",".join(key),))
            seen.add(key)
            out.append({
                "params": params,
                "value": value,
                "cremona_label": cremona_label,
                "lmfdb_label": lmfdb_label,
            })

    _RECORDS = out
    _BY_KEY = {record_key(row["params"]): row for row in out}
    return _RECORDS


def row_for(params):
    if _BY_KEY is None:
        records()
    return _BY_KEY[tuple(str(params[name]) for name in ("N", "c4", "c6", "p"))]


class EllipticCurveMTTLInvariants(numberdb.Generator):
    table = TABLE
    parameters = ("N", "c4", "c6", "p")
    type = "Qp"
    digits = DECIMAL_DIGITS
    rigour = "heuristic (agreement-checked)"

    def enumerate(self):
        for row in records():
            yield dict(row["params"])

    def value(self, params, digits):
        row = row_for(params)
        return {
            "number": row["value"],
            "comment": entry_comment(row["cremona_label"], row["lmfdb_label"]),
        }


if __name__ == "__main__":
    key_from_stdin()
    generator = EllipticCurveMTTLInvariants()
    if os.environ.get("NUMBERDB_PUBLISH") == "1" or "--publish" in sys.argv:
        print(generator.publish(
            message="MTT L-invariants for split multiplicative elliptic curves"))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)