// Try to verify that a given set of Q(t)-points // on an elliptic curve E over Q(t) with full Q(t)-rational 2-torsion // generates E(Q(t)). // // Method: Use specializations at rational values for t // to get a tight upper bound on the image of E(Q(t)) // in H^1(Q(t), E) = (Q(t)^*/Q(t)^*2)^3; // use a criterion due to Gusic & Tadic to get injectivity // of the specialization homomorphism E(Q(t)) --> E_tau(Q). // // M. Stoll, started 2016-03-09 // Canonical representative of image of a nonzero rational number // in Q^x modulo squares. function sqfp(n) s := SquarefreeFactorization(Numerator(n)*Denominator(n)); return s; end function; // (Multiplicative) height of a rational number. function ht(x) return Max(Abs(Numerator(x)), Denominator(x)); end function; // Sign of a rational (or real) number as element of F_2. function sgn(x) return x gt 0 select GF(2)!0 else GF(2)!1; end function; // Given the invariants of two finitely generated abelian groups // containing a third group G, find the tightest upper bound for // the invariants of G that is implied by this information. function groupgcd(invs1, invs2) // ranks of the free parts rk1 := #[i : i in invs1 | i eq 0]; rk2 := #[i : i in invs2 | i eq 0]; // invariants of the torsion parts, with largest factors first fin1 := Reverse([i : i in invs1 | i ne 0]); fin2 := Reverse([i : i in invs2 | i ne 0]); // upper bound for torsion is given by element-wise gcds fin := Reverse([GCD(fin1[i], fin2[i]) : i in [1..Min(#fin1, #fin2)]]); // upper bound for rank is minimum of the ranks return fin cat [0 : i in [1..Min(rk1, rk2)]]; end function; // We represent E by polynomials a, b with integral coefficients, // so that E is given by y^2 = x (x - a) (x - b). // We represent points by their x-coordinates. // The following function tries to verify that the group E(Q(t)) // is generated by its 2-torsion together with the points with x-coordinates // in ptseq. // It returns three values: // * a flag indicating success or failure // * an associative array containing the information from the various // specializations considered // * the linear subspace of H giving the upper bound for the image // of E(Q(t)) function testMW(a, b, ptseq : htbd := 5) // a, b: polynomials in Z[t] // ptseq: sequence of elements of Q(t); x-coordinates of points on E // htbt: bound for height of specializations. E := EllipticCurve([0,-a-b,0,a*b,0]); PZ := PolynomialRing(Integers()); FPZ := FieldOfFractions(PZ); a0 := FPZ!a; b0 := FPZ!b; assert Denominator(a0) eq 1 and Denominator(b0) eq 1; a1 := PZ!Numerator(a0); b1 := PZ!Numerator(b0); // prime divisors in Z[t] of the discriminant, plus generator of units bad := [-1] cat Setseq(&join[{e : e in Factorization(p)} : p in [a1, b1, a1-b1]]); vprintf User1: "generators of bounding subgroup:\n%o\n", bad; V0 := KSpace(GF(2), #bad); basV0 := [V0.i : i in [1..#bad]]; // map to V0 toV0 := func

; V := KSpace(GF(2), 3*#bad); // map x-coordinate to V function toV(p) if p eq 0 then im2 := toV0(-a0); im3 := toV0(-b0); // patch the first component return V!(Eltseq(im2+im3) cat Eltseq(im2) cat Eltseq(im3)); elif p eq a0 then im1 := toV0(a0); im3 := toV0(a0-b0); // patch the second component return V!(Eltseq(im1) cat Eltseq(im1+im3) cat Eltseq(im3)); elif p eq b0 then im1 := toV0(b0); im2 := toV0(b0-a0); // patch the third component return V!(Eltseq(im1) cat Eltseq(im2) cat Eltseq(im1+im2)); else return V!(Eltseq(toV0(p)) cat Eltseq(toV0(p-a0)) cat Eltseq(toV0(p-b0))); end if; end function; // restrict to kernel of the norm H := Kernel(hom V0 | basV0 cat basV0 cat basV0>); vprintf User1: "upper bound for Selmer rank: %o\n", Dimension(H); // find rank of known subgroup if Universe(ptseq)!a notin ptseq then Append(~ptseq, a); end if; if Universe(ptseq)!b notin ptseq then Append(~ptseq, b); end if; // construct points on E from the given x-coordinates points := [E|]; for xc in ptseq do pts := Points(E, xc); assert not IsEmpty(pts); // we only need one point out of each pair of opposite points Append(~points, pts); end for; // compute the height pairing matrix to get the rank htmat := HeightPairingMatrix(points); rk := Rank(htmat); vprintf User1: "rank of known subgroup: %o\n", rk; // find image of known subgroup Hknown := sub; rkknown := Dimension(Hknown) - 2; vprintf User1: "rank of image in H^1(E): %o\n", rkknown; if rk ne rkknown then vprintf User1: "group generated by given points is not saturated at 2!\n"; end if; // implement saturation at 2 if necessary? // this is the current upper bound for the image of E(Q(t)) in H currH := H; // consider specializations specs := {m/n : m in [-htbd..htbd], n in [1..htbd]}; // reduce to nonsingular ones specs := {q : q in specs | Evaluate(a,q) ne 0 and Evaluate(b,q) ne 0 and Evaluate(a-b,q) ne 0}; // sort according to increasing height specs := Sort(Setseq(specs), func); // set up a hashtable for storing the information on the specializations specdata := AssociativeArray(specs); vprintf User1: "\n"; for tau in specs do vprintf User1: "specialization at t = %o:\n", tau; // specialize the curve atau := Evaluate(a, tau); btau := Evaluate(b, tau); Etau := EllipticCurve([0,-atau-btau,0,atau*btau,0]); Etaumin, toEtaumin := MinimalModel(Etau); // try to find its Mordell-Weil group vprintf User1: " computing Mordell-Weil group of E_%o...\n", tau; vtime User1: MW, MWtoE, flag1, flag2 := MordellWeilGroup(Etaumin); if not (flag1 and flag2) then vprintf User1: " group could not be determined. Skipping t = %o\n", tau; else // MW group has been determined vprintf User1: " --> structure %o\n", Invariants(MW); MWtoE := MWtoE*Inverse(toEtaumin); // factor basis for specialized group badtau := [-1] cat Setseq(&join[Set(PrimeDivisors(Numerator(x))) join Set(PrimeDivisors(Denominator(x))) : x in [atau, btau, atau-btau]]); // construct "local" version of V0, V, etc. V0tau := KSpace(GF(2), #badtau); Vtau := KSpace(GF(2), 3*#badtau); toV0tau := func; function toVtau(x) if x eq 0 then im2 := toV0tau(-atau); im3 := toV0tau(-btau); // patch the first component return Vtau!(Eltseq(im2+im3) cat Eltseq(im2) cat Eltseq(im3)); elif x eq atau then im1 := toV0tau(atau); im3 := toV0tau(atau-btau); // patch the second component return Vtau!(Eltseq(im1) cat Eltseq(im1+im3) cat Eltseq(im3)); elif x eq btau then im1 := toV0tau(btau); im2 := toV0tau(btau-atau); // patch the third component return Vtau!(Eltseq(im1) cat Eltseq(im2) cat Eltseq(im1+im2)); else return Vtau!(Eltseq(toV0tau(x)) cat Eltseq(toV0tau(x-atau)) cat Eltseq(toV0tau(x-btau))); end if; end function; // construct specialization map V --> V_tau imbasV0 := [toV0tau(Evaluate(b, tau)) : b in bad]; zeros := [GF(2)!0 : i in [1..#badtau]]; // zero vector for padding imbasV := [Vtau!(Eltseq(imb) cat zeros cat zeros) : imb in imbasV0] cat [Vtau!(zeros cat Eltseq(imb) cat zeros) : imb in imbasV0] cat [Vtau!(zeros cat zeros cat Eltseq(imb)) : imb in imbasV0]; rtau := hom Vtau | imbasV>; // find image of the Mordell-Weil group in V_tau imMW := sub; // store the relevant information specdata[tau] := ; // update the upper bound currH := currH meet (imMW @@ rtau); assert Hknown subset currH; // sanity check vprintf User1: " upper bound for Selmer rank now: %o\n", Dimension(currH) - 2; if currH eq Hknown then vprintf User1: "\nupper bound equals known image\n"; // check if we get injectivity for some tau injs := []; for tt in Keys(specdata) do if Dimension(Kernel(specdata[tt]) meet currH) eq 0 then Append(~injs, tt); end if; end for; vprintf User1: "found %o value(s) such that specialization is injective\n", #injs; if not IsEmpty(injs) then // get upper bound for quotient E(Q(t))/known subgroup flag := false; // used to treat first bound differently quotbound := []; // make quotbound defined (its initial value is not used) for tt in injs do Ett := specdata[tt]; // E_tt // the map E(Q(t)) --> E_tt(Q) toEtt := func; // determine torsion subgroup of E_tt (could also be extracted from MW group info) T, mT := TorsionSubgroup(Ett); MW := specdata[tt]; // the abstract group E_tt(Q) MWtoE := specdata[tt]; // the isomorphism MW --> E_tt(Q) // construct inverse map htmatt := HeightPairingMatrix([MWtoE(MW.j) : j in [3..Ngens(MW)]]); // this does it for the torsion subgroup: mTinv := map<{mT(t) : t in T} -> MW | [ : g in sub]>; // the generators of the free part of the Mordell-Weil group free := [MWtoE(MW.j) : j in [3..Ngens(MW)]]; // now construct the map: // get the free part from the height pairing matrix, then adjust for torsion EtoMW := func; // determine the subgroup of MW coming from the image of the known part of E(Q(t)) imMW := sub; // the following quotient bounds E(Q(t))/E(Q(t))_known Q := quo; // update the bound if flag then quotbound := groupgcd(quotbound, Invariants(Q)); else quotbound := Invariants(Q); flag := true; end if; vprintf User1: "t = %o --> bound for invariants of quotient: %o\n", tt, quotbound; if IsEmpty(quotbound) then vprintf User1: "\nhave verified that known subgroup is everything!\n"; return true, specdata, currH; end if; end for; // tt in injs end if; // not IsEmpty(injs) end if; // currH eq Hknown end if; // not (flag1 and flag2) end for; // tau in specs return false, specdata, currH; end function; // This is a wrapper function that works with an elliptic curve E // over Q(t) and a sequence of points on E. // We do not require that E has the form y^2 = x(x-a)(x-b), // only that it has full Q(t)-rational 2-torsion. function VerifyMWGenerators(E, pts : HeightBound := 5) Qt := BaseRing(E); // check that E is over Q(t) error if Type(Qt) ne FldFunRat or Rank(Qt) ne 1 or BaseField(Qt) cmpne Rationals(), "E must be defined over the function field Q(t)"; // check that E has full Q(t)-rational 2-torsion xseq := [e : e in Roots(DivisionPolynomial(E, 2))]; error if #xseq ne 3, "E must have full Q(t)-rational 2-torsion"; // transform input into format for testMW and call testMW flag := testMW(xseq-xseq, xseq-xseq, [pt/pt-xseq : pt in pts | pt ne 0] : htbd := HeightBound); return flag; end function; /* Here are the examples from "Diagonal genus 5 curves..." Qt := FunctionField(Rationals()); a1 := t-1; a2 := t+1; a3 := 4*t; a4 := 4*t*(4*t^2-1); PQt := PolynomialRing(Qt); // the first four curves are all constructed from three of a1,a2,a3,a4 EC := func;> E1 := EC(a1,a2,a3); E2 := EC(a1,a2,a4); E3 := EC(a1,a3,a4); E4 := EC(a2,a3,a4); E5 := EllipticCurve([0,a+b,0,a*b,0]) where a,b := Explode([16*t*(2*t^2-1), (3*t+1)*(4*t+1)*(4*t^2-t-1)]); assert VerifyMWGenerators(E1, [Points(E1, 0)]); assert VerifyMWGenerators(E2, [Points(E2, x) : x in [0, -8*t^2*(2*t^2-1)]]); assert VerifyMWGenerators(E3, [Points(E3, x) : x in [0, 4*t*(t-1)*(2*t-1)*(4*t+1)]]); assert VerifyMWGenerators(E4, [Points(E4, x) : x in [0, 4*t*(t+1)*(2*t+1)*(4*t-1)]]); assert VerifyMWGenerators(E5, [Points(E5, 2*(3*t+1))]); */