////////////////////////////////////////////////////////// // // // Solving conics over function fields // // // // // // Authors: John Cremona and David Roberts // // // // Functions for finding rational points on conics // // defined over rational function fields, and // // parametrizing all the points // // // // Algorithm: see Cremona and van Hoeij, // // J T de Nombres de Bordeaux (to appear)// // + Simon for parametrization // // // // Last edited: 2006-07-20 // // // ////////////////////////////////////////////////////////// // Some creation functions intrinsic Conic(S::SeqEnum[RngElt]) -> CrvCon {for a sequence of coefficients [a,b,c,d,e,f] returns the conic defined by a*X^2+b*Y^2+c*Z^2+d*X*Y+e*Y*Z+f*X*Z or for a sequence [a,b,c] returns the conic a*X^2+b*Y^2+c*Z^2} require #S in [3,6]: "number of coefficents should equal 3 or 6"; P2:=ProjectivePlane(Parent(S[1])); if #S eq 3 then eqn:=S[1]*X^2+S[2]*Y^2+S[3]*Z^2; else eqn:=S[1]*X^2+S[2]*Y^2+S[3]*Z^2+S[4]*X*Y+S[5]*Y*Z+S[6]*X*Z; end if; require eqn ne 0: "Coefficients cannot all be zero"; C:=Scheme(P2,eqn); require IsConic(C): "IsConic fails for this curve"; _,Con:=IsConic(C); return Con; end intrinsic; intrinsic Matrix(C::CrvCon) -> AlgMatElt {Returns the (symmetric) defining matrix M of the conic C i.e. C is given by the equation [X,Y,Z]*M*[X,Y,Z]T where T denotes transpose.} P:=ProjectivePlane(BaseRing(C)); f:=CoordinateRing(P)!DefiningPolynomial(C); MC:=MonomialCoefficient; S:=[MC(f,[2,0,0]),MC(f,[0,2,0]),MC(f,[0,0,2]), MC(f,[1,1,0]),MC(f,[0,1,1]),MC(f,[1,0,1])]; M:=Matrix([[S[1],S[4]/2,S[6]/2],[S[4]/2,S[2],S[5]/2],[S[6]/2,S[5]/2,S[3]]]); return M; end intrinsic; intrinsic Conic(M::AlgMatElt) -> CrvCon {Returns the conic defined by the 3 x 3 matrix M} require (NumberOfRows(M) eq 3) and (NumberOfColumns(M) eq 3): "Argument must be a non-zero 3 x 3 matrix"; require M ne 0: "Matrix must be non-zero"; S:=[]; for i in [1..3] do S[i]:=M[i,i]; end for; S[4]:=M[1,2]+M[2,1];S[5]:=M[2,3]+M[3,2];S[6]:=M[1,3]+M[3,1]; C:=Conic(S); return C; end intrinsic; ////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// // Solving diagonal conics: non diagonal conics will reduce to // // this case by completing the square. // // From conicFTdiag.m // /////////////////////////////////////////////////////////////////// // // A function to solve diagonal conics over Q, just interfacing to the // built-in magma function: // ///////////////////////////////////////////////////////////////// debug:=false; function solve_conic_diag_Q(abc) // abc=[a,b,c] = diagonal coeffs a,b,c:=Explode(abc); Q:=FieldOfFractions(Parent(a)); assert Type(Q) eq FldRat; a:=Q!a; b:=Q!b; c:=Q!c; P2:=ProjectivePlane(Q); C0:=Curve(P2,a*X^2+b*Y^2+c*Z^2); _,C:=IsConic(C0); if not HasRationalPoint(C) then return false,_; end if; Pl:=RationalPoint(C); xl,yl,zl:=Explode(Eltseq(Pl)); dl:=Lcm(Denominator(xl),Denominator(yl)); xl*:=dl; yl*:=dl; zl*:=dl; return true,[xl,yl,zl]; end function; ///////////////////////////////////////////////////////////////// // // A function to solve diagonal conics over a finite field, just // interfacing to the built-in magma function: // ///////////////////////////////////////////////////////////////// function solve_conic_diag_Fq(abc) // abc=[a,b,c] = diagonal coeffs a,b,c:=Explode(abc); F:=Parent(a); assert Type(F) eq FldFin; P2:=ProjectivePlane(F); C0:=Curve(P2,a*X^2+b*Y^2+c*Z^2); _,C:=IsConic(C0); if not HasRationalPoint(C) then return false,_; end if; Pl:=RationalPoint(C); xl,yl,zl:=Explode(Eltseq(Pl)); return true,[xl,yl,zl]; end function; ///////////////////////////////////////////////////////////////// // // Function to do square-free factorization of polynomials, i.e. // given f, write f=g*h^2 where g is squarefree. Use Magma's // SquareFreeFactorization() function // ///////////////////////////////////////////////////////////////// function SQFF(f) if Degree(f) lt 2 then return f,Parent(f)!1; end if; h:=&*[p[1]^(p[2] div 2) : p in SquareFreeFactorization(f) ]; g:=f div h^2; assert f eq g*h^2; assert IsSquarefree(g); return g,h; end function; ///////////////////////////////////////////////////////////////// // // Main function to solve diagonal conics; coefficients should lie in // a field K which is either Q or finite or of the form F(T). In the // latter case we actually require a,b,c to be polynomials in F[T] // ///////////////////////////////////////////////////////////////// // // I do not know how to test for this in Magma properly, so if the // input has the wrong type the function will fail in strange ways. // ///////////////////////////////////////////////////////////////// function solve_conic_diag(abc) // abc=[a,b,c] = diagonal coeffs a,b,c:=Explode(abc); F:=FieldOfFractions(Universe(abc)); R:=Integers(F); denom:=LCM([Denominator(F!x): x in abc]); a,b,c:=Explode([R|x*denom: x in abc]); FT:=Parent(a); if 0 in abc then i:=Index(abc,0); r:=[FT|0,0,0]; r[i]:=1; return true,r; end if; if debug then print "In solve_conic_diag() with [a,b,c] = ",abc," over ",FT; end if; // Detect if the equation is defined over Q or a finite field where // Magma already has conic-solving capability: if Type(FT) eq FldRat or Type(FT) eq RngInt then return solve_conic_diag_Q(abc); end if; if Type(FT) eq FldFin then return solve_conic_diag_Fq(abc); end if; // Define the Base field F and the variable T such that the conic is // defined over F(T): T:=FT.1; F:=BaseRing(FT); // Reduce to the case a*b*c square-free; Xscale, Yscale, Zscale store // the scalings for the transformation back, and the flag scaled // records whether or not any scaling has in fact been done: scaled:=false; Xscale:=1; Yscale:=1; Zscale:=1; a0:=a; b0:=b; c0:=c; // keep the original coefficients // Reduction 1: remove any common factors of all three coefficients. // No scaling of solution needed g:=Gcd(Gcd(a,b),c); if Degree(g) gt 0 then if debug then print "dividing out by a common factor ",g; end if; a div:=g; b div:=g; c div:=g; end if; // Reduction 2: remove any square factors of all three coefficients. // Scaling of solution needed a1,a2:=SQFF(a); // a=a1*a2^2 with a1 square-free if Degree(a2) gt 0 then a:=a1; Yscale*:=a2; Zscale*:=a2; scaled:=true; end if; b1,b2:=SQFF(b); // b=b1*b2^2 with b1 square-free if Degree(b2) gt 0 then b:=b1; Xscale*:=b2; Zscale*:=b2; scaled:=true; end if; c1,c2:=SQFF(c); // c=c1*c2^2 with c1 square-free if Degree(c2) gt 0 then c:=c1; Xscale*:=c2; Yscale*:=c2; scaled:=true; end if; // Reduction 3: remove any common factors of any two coefficients. // Scaling of solution needed while Degree(Gcd(a,b)) gt 0 or Degree(Gcd(b,c)) gt 0 or Degree(Gcd(a,c)) gt 0 do g:=Gcd(a,b); if Degree(g) gt 0 then a div:=g; b div:=g; c*:=g; Zscale*:=g; scaled:=true; end if; g:=Gcd(b,c); if Degree(g) gt 0 then b div:=g; c div:=g; a*:=g; Xscale*:=g; scaled:=true; end if; g:=Gcd(a,c); if Degree(g) gt 0 then a div:=g; c div:=g; b*:=g; Yscale*:=g; scaled:=true; end if; end while; if debug then print "...after reducing, [a,b,c] = ",[a,b,c]; end if; // Check that after all that we now have a "constant" equation: // if so, use recursion if Degree(a) eq 0 and Degree(b) eq 0 and Degree(c) eq 0 then if debug then print "...constant coefficients!"; end if; flag,Pl:=$$([F!a,F!b,F!c]); if flag then // Coerce back into FT and rescale to original variables: xsol:=FT!(Pl[1]); ysol:=FT!(Pl[2]); zsol:=FT!(Pl[3]); if scaled then xsol*:=Xscale; ysol*:=Yscale; zsol*:=Zscale; end if; return true, [xsol,ysol,zsol]; else // flag is false: no solution return false,_; end if; end if; // constant coefficient case // // Factorize the coefficients to find the bad places, and their degrees: // pa:=[p[1] : p in Factorization(a)]; dpa:=[Degree(p): p in pa]; if debug then print "a degrees: ",dpa;end if; pb:=[p[1] : p in Factorization(b)]; dpb:=[Degree(p): p in pb]; if debug then print "b degrees: ", dpb; end if; pc:=[p[1] : p in Factorization(c)]; dpc:=[Degree(p): p in pc]; if debug then print "c degrees: ", dpc; end if; // Decide between case 0 (equal degree parities and no degree 1 bad // place) and case 1 (unequal degree parities or a degree 1 bad place, // in which case one degree 1 bad place (only) is removed from one of // the support sets): Case:=1; da:=Degree(a);db:=Degree(b);dc:=Degree(c); if (da-db) mod 2 eq 0 and (da-dc) mod 2 eq 0 then Case:=0; // but if there's a degree one factor of a,b, or c... id:=Index(dpa,1); if id ne 0 then Case:=1; pa:=Remove(pa,id); if debug then print "Removing linear factor from a, resetting Case to 1"; end if; else id:=Index(dpb,1); if id ne 0 then Case:=1; pb:=Remove(pb,id); if debug then print "Removing linear factor from b, resetting Case to 1"; end if; else id:=Index(dpc,1); if id ne 0 then Case:=1; pc:=Remove(pc,id); if debug then print "Removing linear factor from c, resetting Case to 1"; end if; end if; end if; end if; end if; // Set A, B, C (the degrees of X,Y,Z in the solution): A:=Ceiling((db+dc)/2)-Case; B:=Ceiling((da+dc)/2)-Case; C:=Ceiling((da+db)/2)-Case; if debug then print "A=",A, " B=",B, " C=",C; end if; // In case 0, solve the leading-coefficient equation: if Case eq 0 then la:=LeadingCoefficient(a); lb:=LeadingCoefficient(b); lc:=LeadingCoefficient(c); if debug then print "Solving leading-coefficient conic with coeffs ", [la,lb,lc]," over ",Parent(la); end if; if Type(Parent(la)) eq FldRat or Type(Parent(la)) eq RngInt then flag,Pl:= solve_conic_diag_Q([la,lb,lc]); else if Type(Parent(la)) eq FldFin then flag,Pl:= solve_conic_diag_Fq([la,lb,lc]); else // Use recursion: but first we must convert the leading coefficients // from rational functions to polynomials. This will cause a run-time // error if the base field F is not suitable! // g:=Lcm([Denominator(l) : l in [la,lb,lc]]); la:=Numerator(g*la); lb:=Numerator(g*lb); lc:=Numerator(g*lc); flag,Pl:=$$([la,lb,lc]); end if; end if; // If the leading coefficient equation has no solution, quit now: if debug then print "Soluble? ",flag; end if; if not flag then return false,_; end if; // Otherwise use it to form the first three equations: xl,yl,zl:=Explode(Pl); if debug then print "leading coeff solution = ",Pl; end if; // Matrix Mp will hold the coefficients of the linear equations to be // solved; for technical reasons each equation is in a column of Mp. // First we have 3 columns from the leading-coefficient conic solution // (here case=0) // // Mplist is a list of such matrices, which will in the end be // concatenated Mp:=Transpose(Matrix([ [F!0 : i in [0..A-1]] cat [F!1] cat [F!0 : j in [0..B]] cat [F!0 : k in [0..C]] cat [F!-xl], [F!0 : i in [0..A]] cat [F!0 : j in [0..B-1]] cat [F!1] cat [F!0 : k in [0..C]] cat [F!-yl], [F!0 : i in [0..A]] cat [F!0 : j in [0..B]] cat [F!0 : k in [0..C-1]] cat [F!1] cat [F!-zl] ] )); //"Matrix from l.c. condition = ",Mp; Mplist:=; else // case=1 Mplist:=<>; end if; // Here ends the case=0/1 division // Now make up the rest of the matrices of the linear system; each // degree d factor of a, b or c contributes d more columns (except for // a linear factor if we have excluded one earlier); the number of // rows is A+B+C+3, and the number of extra columns is either deg(abc) // or deg(abc)-1. for p in pa do // process p|a d:=Degree(p); //print "Processing factor ",p," of a of degree ",d; Fp:=quo; r:=(-(Fp!c)/(Fp!b)); //print "Taking square root of", r; flag,r:=IsSquare(r); //print "result: ",flag; if flag then print "sqrt = ",r; end if; if not flag then return false,p; end if; // no local certificate // The following line is only for testing; it ensures that repeated // running of the same examples should give different solutions! //r*:=Random({-1,1}); r:=FT!r; assert ((b*r^2+c) mod p) eq 0; // we have a local certificate, and construct d new columns: Mp:=Matrix( [[F!0: l in [0..d-1]] : i in [0..A]] cat [[F!Coefficient(T^j mod p,l): l in [0..d-1]] : j in [0..B]] cat [[F!Coefficient(-r*T^k mod p,l): l in [0..d-1]] : k in [0..C]] cat [[F!0: l in [0..d-1]] : l in [1..1-Case]] ); Append(~Mplist,Mp); end for; for p in pb do // process p|b d:=Degree(p); //print "Processing factor ",p," of b of degree ",d; Fp:=quo; r:=(-(Fp!a)/(Fp!c)); //print "Taking square root of", r, " in ",Fp; flag,r:=IsSquare(r); //print "result: ",flag; if flag then print "sqrt = ",r; end if; if not flag then return false,p; end if; //r*:=Random({-1,1}); r:=FT!r; assert ((c*r^2+a) mod p) eq 0; // we have a local certificate, and construct d new columns: Mp:=Matrix( [[F!Coefficient(-r*T^i mod p,l): l in [0..d-1]] : i in [0..A]] cat [[F!0: l in [0..d-1]] : j in [0..B]] cat [[F!Coefficient(T^k mod p,l): l in [0..d-1]] : k in [0..C]] cat [[F!0: l in [0..d-1]] : l in [1..1-Case]] ); Append(~Mplist,Mp); end for; for p in pc do // process p|c d:=Degree(p); //print "Processing factor ",p," of c of degree ",d; Fp:=quo; r:=(-(Fp!b)/(Fp!a)); //print "Taking square root of", r; flag,r:=IsSquare(r); //print "result: ",flag; if flag then print "sqrt = ",r; end if; if not flag then return false,p; end if; //r*:=Random({-1,1}); r:=FT!r; assert ((a*r^2+b) mod p) eq 0; // we have a local certificate, and construct d new columns: Mp:=Matrix( [[F!Coefficient(T^i mod p,l): l in [0..d-1]] : i in [0..A]] cat [[F!Coefficient(-r*T^j mod p,l): l in [0..d-1]] : j in [0..B]] cat [[F!0: l in [0..d-1]] : k in [0..C]] cat [[F!0: l in [0..d-1]] : l in [1..1-Case]] ); Append(~Mplist,Mp); end for; // Now Mplist contains all the equations we need to solve, as a list // of matrices. Concatenate them and find the kernel. Since we know // from theory that it has positive dimension, we pick out the first // basis vector and convert it to a list: K:=Eltseq(Kernel(HorizontalJoin(Mplist)).1); //print "Kernel = ",K; // The entries in K are the coefficients of the solution X,Y,Z (in // that order); if we added 3 new variables for case 0 we can now // ignore them. xsol:=&+([0] cat [K[i]*T^(i-1) : i in [1..A+1]]); ysol:=&+([0] cat [K[A+1+j]*T^(j-1) : j in [1..B+1]]); zsol:=&+([0] cat [K[A+B+2+k]*T^(k-1) : k in [1..C+1]]); // Rescale to original variables if necessary: if scaled then xsol*:=Xscale; ysol*:=Yscale; zsol*:=Zscale; end if; // Remove any common factors: g:=Gcd([xsol,ysol,zsol]); if Degree(g) gt 0 then xsol div:=g; ysol div:=g; zsol div:=g; end if; // Check we do have a solution! (For debugging only) lhs:=a0*xsol^2+b0*ysol^2+c0*zsol^2; if debug then print "LHS = ",lhs; end if; assert lhs eq 0; return true,[xsol,ysol,zsol]; end function; // solve_conic_diag() ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// // Function for solving "semi-diagonal" conics. // ////////////////////////////////////////////////////////// // Solve a conic of the form Y^2 = a*X^2 + b*X*Z + c*Z^2 function solve_semi(ABC); // ABC is [a,b,c] with the conic in the above form // complete the square then solve and parametrize the diagonal conic a,b,c:=Explode(ABC); F:=FieldOfFractions(Parent(a)); C:=Conic([F|a,-1,c,0,0,b]); flag,p:=solve_conic_diag([1,-a,a*c-b^2/4]); if flag then x,y,z:=Explode(Eltseq(p)); X:=(x-(b/2)*z)/a; p1:=[X,y,z]; assert IsCoercible(C,p1); return true,p1; end if; return false,_; end function; /////////////////////////////////////////////////////////// // Function for general conics // /////////////////////////////////////////////////////////// // Solve a conic of the form a*X^2 + b*Y^2 + c*Z^2 + d*X*Y + e*Y*Z + f*X*Z = 0 // reduces to solving a semi-diagonal conic by completing the square. function solve_general(cfs); a,b,c,d,e,f:=Explode(cfs); K:=Universe(cfs); C:=Conic(cfs); if a eq 0 then return [K|1,0,0]; end if; if b eq 0 then return [K|0,1,0]; end if; if c eq 0 then return [K|0,0,1]; end if; cfs_a:=[x/a: x in cfs]; _,b1,c1,d1,e1,f1:=Explode(cfs_a); flag,p:=solve_semi([(d1^2)/4-b1,(d1*f1)/2-e1,(f1^2)/4-c1]); if flag then u,w,v:=Explode(Eltseq(p)); Y:=u; Z:=v; X:=w-((d1*u)/2+(f1*v)/2); assert IsCoercible(C,[X,Y,Z]); return true,[X,Y,Z]; end if; return false,_; end function; //////////////////////////////////////////////////////////// // Parametrization of Conics // //////////////////////////////////////////////////////////// primitivecoords:=function(p); p:=Eltseq(p); K:=Parent(p[1]); R:=RingOfIntegers(K); L:=LCM([Denominator(x): x in p]); p:=[R!(L*x): x in p]; G:=GCD(p); p:=[R!(x/G): x in p]; return(p); end function; ds_paramP:=function(Con,p); // see Denis Simon - "Sur la parametrisation des // solutions des equations quadratiques" if (p[1] eq 0) and (p[2] eq 0) then return Parametrization(Con,Con!p); end if; // above line is to temporarily avoid a bug when p is of the form [0,0,z]; FC:=DefiningEquation(Con); MC:=Matrix(Con); P:=AmbientSpace(Con); K:=FieldOfFractions(BaseRing(P)); R:=RingOfIntegers(K); p:=primitivecoords(p); G:=GCD(p); p:=[R!(x/G): x in p]; assert GCD(p) eq 1; a:=p[1];b:=p[2];c:=p[3]; g,u,v:=Xgcd(a,b); assert g eq a*u+b*v; h,w,z:=Xgcd(g,c); assert h eq 1; assert g*w+c*z eq 1; M:=Matrix([[R|a,b,c],[R|-v,u,0],[R|-a*z/g,-b*z/g,w]]); assert Determinant(M) eq 1; Mt:=Transpose(M); qp:=M*MC*Mt; assert qp[1][1] eq 0; qh:=Matrix([[qp[2][2],2*qp[2][3],qp[3][3]],[-2*qp[1][2],-2*qp[1][3],0],[0,-2*qp[1][2],-2*qp[1][3]]]); q:=Mt*qh; q1,q2,q3:=Explode(RowSequence(q)); P1:=ProjectiveSpace(K,1); eq1:=q1[1]*P1.1^2+q1[2]*P1.1*P1.2+q1[3]*P1.2^2; eq2:=q2[1]*P1.1^2+q2[2]*P1.1*P1.2+q2[3]*P1.2^2; eq3:=q3[1]*P1.1^2+q3[2]*P1.1*P1.2+q3[3]*P1.2^2; par:=[eq1,eq2,eq3]; m:=mapCon|par>; return m; end function; /////////////////////////////////////////////////////////// // intrinsics intrinsic HasRationalPoint_FldFun(C::CrvCon) -> BoolElt,Pt {Returns true if the Conic C has a point} M:=Matrix(C); K:=BaseRing(M); require Characteristic(K) ne 2: "Base-ring of conic cannot have characteristic 2"; if IsDiagonal(M) then abc:=[M[i,i]: i in [1..3]]; flag,p:=solve_conic_diag(abc); if flag then return true,C!p; end if; return false,_; end if; if [M[1,2],M[2,1],M[2,3],M[3,2]] eq [K|0,0,0,0] then // semi-diagonal case a:=-M[2,2]; if a ne 0 then flag,p:=solve_semi([M[1,1]/a,2*M[1,3]/a,M[3,3]/a]); if flag then return true,C!p; end if; return false,_; end if; return true,C![0,1,0]; end if; flag,p:=solve_general([M[1,1],M[2,2],M[3,3],2*M[1,2],2*M[2,3],2*M[1,3]]); if flag then return true,C!p; end if; return false,_; end intrinsic; /////////////////////////////////////////////////////////////// intrinsic DS_Parametrization(C::CrvCon) -> MapSch {Parametrization of the conic C} flag,p:=HasRationalPoint_FldFun(C); require flag: "C does not have a rational point"; return ds_paramP(C,p); end intrinsic; intrinsic DS_Parametrization(C::CrvCon,p::Pt) -> MapSch {Parametrization of the conic C with point p} return ds_paramP(C,p); end intrinsic;