Path: blob/master/src/sage/quadratic_forms/binary_qf.py
8817 views
r"""1Binary Quadratic Forms with Integer Coefficients23This module provides a specialized class for working with a binary quadratic4form `a x^2 + b x y + c y^2`, stored as a triple of integers `(a, b, c)`.56EXAMPLES::78sage: Q = BinaryQF([1,2,3])9sage: Q10x^2 + 2*x*y + 3*y^211sage: Q.discriminant()12-813sage: Q.reduced_form()14x^2 + 2*y^215sage: Q(1, 1)1661718TESTS::1920sage: Q == loads(dumps(Q))21True2223AUTHORS:2425- Jon Hanke (2006-08-08):2627- Appended to add the methods :func:`BinaryQF_reduced_representatives`,28:meth:`~BinaryQF.is_reduced`, and ``__add__`` on 8-3-2006 for Coding Sprint29#2.30- Added Documentation and :meth:`~BinaryQF.complex_point` method on 8-8-2006.3132- Nick Alexander: add doctests and clean code for Doc Days 233- William Stein (2009-08-05): composition; some ReSTification.34- William Stein (2009-09-18): make immutable.35"""3637#*****************************************************************************38# Copyright (C) 2006--2009 William Stein and Jon Hanke39#40# Distributed under the terms of the GNU General Public License (GPL)41#42# This code is distributed in the hope that it will be useful,43# but WITHOUT ANY WARRANTY; without even the implied warranty of44# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU45# General Public License for more details.46#47# The full text of the GPL is available at:48#49# http://www.gnu.org/licenses/50#*****************************************************************************5152from sage.libs.pari.all import pari53from sage.rings.all import (is_fundamental_discriminant, ZZ, divisors)54from sage.structure.sage_object import SageObject55from sage.misc.cachefunc import cached_method5657class BinaryQF(SageObject):58"""59A binary quadratic form over `\ZZ`.6061INPUT:6263- `v` -- a list or tuple of 3 entries: [a,b,c], or a quadratic homogeneous64polynomial in two variables with integer coefficients656667OUTPUT:6869the binary quadratic form a*x^2 + b*x*y + c*y^2.7071EXAMPLES::7273sage: b = BinaryQF([1,2,3])74sage: b.discriminant()75-876sage: R.<x, y> = ZZ[]77sage: BinaryQF(x^2 + 2*x*y + 3*y^2) == b78True79"""80# Initializes the form with a 3-element list81def __init__(self, abc):82r"""83Creates the binary quadratic form `ax^2 + bxy + cy^2` from the84triple [a,b,c] over `\ZZ` or from a polynomial.8586INPUT:8788- ``abc`` -- 3-tuple of integers, or a quadratic homogeneous polynomial89in two variables with integer coefficients9091EXAMPLES::9293sage: Q = BinaryQF([1,2,3]); Q94x^2 + 2*x*y + 3*y^295sage: Q = BinaryQF([1,2])96Traceback (most recent call last):97...98TypeError: Binary quadratic form must be given by a list of three coefficients99100sage: R.<x, y> = ZZ[]101sage: f = x^2 + 2*x*y + 3*y^2102sage: BinaryQF(f)103x^2 + 2*x*y + 3*y^2104sage: BinaryQF(f + x)105Traceback (most recent call last):106...107TypeError: Binary quadratic form must be given by a quadratic homogeneous bivariate integer polynomial108109TESTS::110111sage: BinaryQF(0)1120113"""114if isinstance(abc, (list, tuple)):115if len(abc) != 3:116# Check we have three coefficients117raise TypeError, "Binary quadratic form must be given by a list of three coefficients"118self._a, self._b, self._c = [ZZ(x) for x in abc]119else:120f = abc121from sage.rings.polynomial.multi_polynomial_element import is_MPolynomial122if f.is_zero():123self._a, self._b, self._c = [ZZ(0), ZZ(0), ZZ(0)]124elif (is_MPolynomial(f) and f.is_homogeneous() and f.base_ring() == ZZ125and f.degree() == 2 and f.parent().ngens() == 2):126x, y = f.parent().gens()127self._a, self._b, self._c = [f.monomial_coefficient(mon) for mon in [x**2, x*y, y**2]]128else:129raise TypeError, "Binary quadratic form must be given by a quadratic homogeneous bivariate integer polynomial"130131def _pari_init_(self):132"""133Used to convert this quadratic form to Pari.134135EXAMPLES::136137sage: f = BinaryQF([2,3,4]); f1382*x^2 + 3*x*y + 4*y^2139sage: f._pari_init_()140'Qfb(2,3,4)'141sage: pari(f)142Qfb(2, 3, 4)143sage: type(pari(f))144<type 'sage.libs.pari.gen.gen'>145sage: gp(f)146Qfb(2, 3, 4)147sage: type(gp(f))148<class 'sage.interfaces.gp.GpElement'>149"""150return 'Qfb(%s,%s,%s)'%(self._a,self._b,self._c)151152def __mul__(self, right):153"""154Gauss composition of binary quadratic forms. The result is155not reduced.156157EXAMPLES:158159We explicitly compute in the group of classes of positive160definite binary quadratic forms of discriminant -23.161162::163164sage: R = BinaryQF_reduced_representatives(-23); R165[x^2 + x*y + 6*y^2, 2*x^2 - x*y + 3*y^2, 2*x^2 + x*y + 3*y^2]166sage: R[0] * R[0]167x^2 + x*y + 6*y^2168sage: R[1] * R[1]1694*x^2 + 3*x*y + 2*y^2170sage: (R[1] * R[1]).reduced_form()1712*x^2 + x*y + 3*y^2172sage: (R[1] * R[1] * R[1]).reduced_form()173x^2 + x*y + 6*y^2174175"""176if not isinstance(right, BinaryQF):177raise TypeError, "both self and right must be binary quadratic forms"178# There could be more elegant ways, but qfbcompraw isn't179# wrapped yet in the PARI C library. We may as well settle180# for the below, until somebody simply implements composition181# from scratch in Cython.182v = list(pari('qfbcompraw(%s,%s)'%(self._pari_init_(),183right._pari_init_())))184return BinaryQF(v)185186def __getitem__(self, n):187"""188Return the n-th component of this quadratic form.189190If this form is `a x^2 + b x y + c y^2`, the 0-th component is `a`,191the 1-st component is `b`, and `2`-nd component is `c`.192193Indexing is like lists -- negative indices and slices are allowed.194195EXAMPLES::196197198sage: Q = BinaryQF([2,3,4])199sage: Q[0]2002201sage: Q[2]2024203sage: Q[:2]204(2, 3)205sage: tuple(Q)206(2, 3, 4)207sage: list(Q)208[2, 3, 4]209"""210return (self._a, self._b, self._c)[n]211212def __call__(self, *args):213r"""214Evaluate this quadratic form at a point.215216INPUT:217218- args -- x and y values, as a pair x, y or a list, tuple, or219vector220221EXAMPLES::222223224sage: Q = BinaryQF([2, 3, 4])225sage: Q(1, 2)22624227228TESTS::229230sage: Q = BinaryQF([2, 3, 4])231sage: Q([1, 2])23224233sage: Q((1, 2))23424235sage: Q(vector([1, 2]))23624237"""238if len(args) == 1:239args = args[0]240x, y = args241return (self._a * x + self._b * y) * x + self._c * y**2242243def __cmp__(self, right):244"""245Returns True if self and right are identical: the same coefficients.246247EXAMPLES::248249250sage: P = BinaryQF([2,2,3])251sage: Q = BinaryQF([2,2,3])252sage: R = BinaryQF([1,2,3])253sage: P == Q # indirect doctest254True255sage: P == R # indirect doctest256False257258TESTS::259260sage: P == P261True262sage: Q == P263True264sage: R == P265False266sage: P == 2267False268"""269if not isinstance(right, BinaryQF):270return cmp(type(self), type(right))271return cmp((self._a,self._b,self._c), (right._a,right._b,right._c))272273def __add__(self, Q):274"""275Returns the component-wise sum of two forms.276277That is, given `a_1 x^2 + b_1 x y + c_1 y^2` and `a_2 x^2 + b_2 x y +278c_2 y^2`, returns the form279`(a_1 + a_2) x^2 + (b_1 + b_2) x y + (c_1 + c_2) y^2.`280281EXAMPLES::282283284sage: P = BinaryQF([2,2,3]); P2852*x^2 + 2*x*y + 3*y^2286sage: Q = BinaryQF([-1,2,2]); Q287-x^2 + 2*x*y + 2*y^2288sage: P + Q289x^2 + 4*x*y + 5*y^2290sage: P + Q == BinaryQF([1,4,5]) # indirect doctest291True292293TESTS::294295sage: Q + P == BinaryQF([1,4,5]) # indirect doctest296True297"""298return BinaryQF([self._a + Q._a, self._b + Q._b, self._c + Q._c])299300def __sub__(self, Q):301"""302Returns the component-wise difference of two forms.303304That is, given `a_1 x^2 + b_1 x y + c_1 y^2` and `a_2 x^2 + b_2 x y +305c_2 y^2`, returns the form306`(a_1 - a_2) x^2 + (b_1 - b_2) x y + (c_1 - c_2) y^2.`307308EXAMPLES::309310311sage: P = BinaryQF([2,2,3]); P3122*x^2 + 2*x*y + 3*y^2313sage: Q = BinaryQF([-1,2,2]); Q314-x^2 + 2*x*y + 2*y^2315sage: P - Q3163*x^2 + y^2317sage: P - Q == BinaryQF([3,0,1]) # indirect doctest318True319320TESTS::321322sage: Q - P == BinaryQF([3,0,1]) # indirect doctest323False324sage: Q - P != BinaryQF([3,0,1]) # indirect doctest325True326"""327return BinaryQF([self._a - Q._a, self._b - Q._b, self._c - Q._c])328329def _repr_(self):330"""331Display the quadratic form.332333EXAMPLES::334335336sage: Q = BinaryQF([1,2,3]); Q # indirect doctest337x^2 + 2*x*y + 3*y^2338339sage: Q = BinaryQF([-1,2,3]); Q340-x^2 + 2*x*y + 3*y^2341342sage: Q = BinaryQF([0,0,0]); Q3430344"""345return repr(self.polynomial())346347def _latex_(self):348"""349Return latex representation of this binary quadratic form.350351EXAMPLES::352353sage: f = BinaryQF((778,1115,400)); f354778*x^2 + 1115*x*y + 400*y^2355sage: latex(f) # indirect doctest356778 x^{2} + 1115 x y + 400 y^{2}357"""358return self.polynomial()._latex_()359360@cached_method361def polynomial(self):362"""363Returns the binary quadratic form as a homogeneous 2-variable364polynomial.365366EXAMPLES::367368sage: Q = BinaryQF([1,2,3])369sage: Q.polynomial()370x^2 + 2*x*y + 3*y^2371372sage: Q = BinaryQF([-1,-2,3])373sage: Q.polynomial()374-x^2 - 2*x*y + 3*y^2375376sage: Q = BinaryQF([0,0,0])377sage: Q.polynomial()3780379"""380return self(ZZ['x, y'].gens())381382@cached_method383def discriminant(self):384"""385Returns the discriminant `b^2 - 4ac` of the binary386form `ax^2 + bxy + cy^2`.387388EXAMPLES::389390391sage: Q = BinaryQF([1,2,3])392sage: Q.discriminant()393-8394"""395return self._b**2 - 4 * self._a * self._c396397@cached_method398def has_fundamental_discriminant(self):399"""400Checks if the discriminant D of this form is a fundamental401discriminant (i.e. D is the smallest element of its402squareclass with D = 0 or 1 mod 4).403404EXAMPLES::405406sage: Q = BinaryQF([1,0,1])407sage: Q.discriminant()408-4409sage: Q.has_fundamental_discriminant()410True411412sage: Q = BinaryQF([2,0,2])413sage: Q.discriminant()414-16415sage: Q.has_fundamental_discriminant()416False417"""418return is_fundamental_discriminant(self.discriminant())419420@cached_method421def is_primitive(self):422"""423Checks if the form `ax^2 + bxy + cy^2` satisfies424`\gcd(a,b,c)=1`, i.e., is primitive.425426EXAMPLES::427428429sage: Q = BinaryQF([6,3,9])430sage: Q.is_primitive()431False432433sage: Q = BinaryQF([1,1,1])434sage: Q.is_primitive()435True436437sage: Q = BinaryQF([2,2,2])438sage: Q.is_primitive()439False440441sage: rqf = BinaryQF_reduced_representatives(-23*9)442sage: [qf.is_primitive() for qf in rqf]443[True, True, True, False, True, True, False, False, True]444sage: rqf445[x^2 + x*y + 52*y^2,4462*x^2 - x*y + 26*y^2,4472*x^2 + x*y + 26*y^2,4483*x^2 + 3*x*y + 18*y^2,4494*x^2 - x*y + 13*y^2,4504*x^2 + x*y + 13*y^2,4516*x^2 - 3*x*y + 9*y^2,4526*x^2 + 3*x*y + 9*y^2,4538*x^2 + 7*x*y + 8*y^2]454sage: [qf for qf in rqf if qf.is_primitive()]455[x^2 + x*y + 52*y^2,4562*x^2 - x*y + 26*y^2,4572*x^2 + x*y + 26*y^2,4584*x^2 - x*y + 13*y^2,4594*x^2 + x*y + 13*y^2,4608*x^2 + 7*x*y + 8*y^2]461"""462from sage.rings.arith import gcd463return gcd([self._a, self._b, self._c])==1464465@cached_method466def is_weakly_reduced(self):467"""468Checks if the form `ax^2 + bxy + cy^2` satisfies469`|b| \leq a \leq c`, i.e., is weakly reduced.470471EXAMPLES::472473474sage: Q = BinaryQF([1,2,3])475sage: Q.is_weakly_reduced()476False477478sage: Q = BinaryQF([2,1,3])479sage: Q.is_weakly_reduced()480True481482sage: Q = BinaryQF([1,-1,1])483sage: Q.is_weakly_reduced()484True485"""486if self.discriminant() >= 0:487raise NotImplementedError, "only implemented for negative discriminants"488return (abs(self._b) <= self._a) and (self._a <= self._c)489490@cached_method491def reduced_form(self):492"""493Return the unique reduced form equivalent to ``self``. See also494:meth:`~is_reduced`.495496EXAMPLES::497498sage: a = BinaryQF([33,11,5])499sage: a.is_reduced()500False501sage: b = a.reduced_form(); b5025*x^2 - x*y + 27*y^2503sage: b.is_reduced()504True505506sage: a = BinaryQF([15,0,15])507sage: a.is_reduced()508True509sage: b = a.reduced_form(); b51015*x^2 + 15*y^2511sage: b.is_reduced()512True513"""514if self.discriminant() >= 0 or self._a < 0:515raise NotImplementedError, "only implemented for positive definite forms"516if not self.is_reduced():517v = list(pari('Vec(qfbred(Qfb(%s,%s,%s)))'%(self._a,self._b,self._c)))518return BinaryQF(v)519else:520return self521522def is_equivalent(self, right):523"""524Return true if self and right are equivalent, i.e., have the525same reduced form.526527INPUT:528529- ``right`` -- a binary quadratic form530531EXAMPLES::532533sage: a = BinaryQF([33,11,5])534sage: b = a.reduced_form(); b5355*x^2 - x*y + 27*y^2536sage: a.is_equivalent(b)537True538sage: a.is_equivalent(BinaryQF((3,4,5)))539False540"""541if not isinstance(right, BinaryQF):542raise TypeError, "right must be a binary quadratic form"543return self.reduced_form() == right.reduced_form()544545@cached_method546def is_reduced(self):547"""548Checks if the quadratic form is reduced, i.e., if the form549`ax^2 + bxy + cy^2` satisfies `|b|\leq a \leq c`, and550that `b\geq 0` if either `a = b` or `a = c`.551552EXAMPLES::553554sage: Q = BinaryQF([1,2,3])555sage: Q.is_reduced()556False557558sage: Q = BinaryQF([2,1,3])559sage: Q.is_reduced()560True561562sage: Q = BinaryQF([1,-1,1])563sage: Q.is_reduced()564False565566sage: Q = BinaryQF([1,1,1])567sage: Q.is_reduced()568True569"""570return (-self._a < self._b <= self._a < self._c) or \571(ZZ(0) <= self._b <= self._a == self._c)572573def complex_point(self):574r"""575Returns the point in the complex upper half-plane associated576to this (positive definite) quadratic form.577578For positive definite forms with negative discriminants, this is a579root `\tau` of `a x^2 + b x + c` with the imaginary part of `\tau`580greater than 0.581582EXAMPLES::583584sage: Q = BinaryQF([1,0,1])585sage: Q.complex_point()5861.00000000000000*I587"""588if self.discriminant() >= 0:589raise NotImplementedError, "only implemented for negative discriminant"590R = ZZ['x']591x = R.gen()592Q1 = R(self.polynomial()(x,1))593return [z for z in Q1.complex_roots() if z.imag() > 0][0]594595def matrix_action_left(self, M):596r"""597Return the binary quadratic form resulting from the left action598of the 2-by-2 matrix ``M`` on the quadratic form ``self``.599600Here the action of the matrix `M = \begin{pmatrix} a & b \\ c & d601\end{pmatrix}` on the form `Q(x, y)` produces the form `Q(ax+by,602cx+dy)`.603604EXAMPLES::605606sage: Q = BinaryQF([2, 1, 3]); Q6072*x^2 + x*y + 3*y^2608sage: M = matrix(ZZ, [[1, 2], [3, 5]])609sage: Q.matrix_action_left(M)61016*x^2 + 83*x*y + 108*y^2611"""612v, w = M.rows()613a1 = self(v)614c1 = self(w)615b1 = self(v + w) - a1 - c1616return BinaryQF([a1, b1, c1])617618def matrix_action_right(self, M):619r"""620Return the binary quadratic form resulting from the right action621of the 2-by-2 matrix ``M`` on the quadratic form ``self``.622623Here the action of the matrix `M = \begin{pmatrix} a & b \\ c & d624\end{pmatrix}` on the form `Q(x, y)` produces the form `Q(ax+cy,625bx+dy)`.626627EXAMPLES::628629sage: Q = BinaryQF([2, 1, 3]); Q6302*x^2 + x*y + 3*y^2631sage: M = matrix(ZZ, [[1, 2], [3, 5]])632sage: Q.matrix_action_right(M)63332*x^2 + 109*x*y + 93*y^2634"""635v, w = M.columns()636a1 = self(v)637c1 = self(w)638b1 = self(v + w) - a1 - c1639return BinaryQF([a1, b1, c1])640641def BinaryQF_reduced_representatives(D, primitive_only=False):642r"""643Returns a list of inequivalent reduced representatives for the644equivalence classes of positive definite binary forms of645discriminant D.646647INPUT:648649- `D` -- (integer) A negative discriminant.650651- ``primitive_only`` -- (bool, default False) flag controlling whether only652primitive forms are included.653654OUTPUT:655656(list) A lexicographically-ordered list of inequivalent reduced657representatives for the equivalence classes of positive definite binary658forms of discriminant `D`. If ``primitive_only`` is ``True`` then659imprimitive forms (which only exist when `D` is not fundamental) are660omitted; otherwise they are included.661662EXAMPLES::663664sage: BinaryQF_reduced_representatives(-4)665[x^2 + y^2]666667sage: BinaryQF_reduced_representatives(-163)668[x^2 + x*y + 41*y^2]669670sage: BinaryQF_reduced_representatives(-12)671[x^2 + 3*y^2, 2*x^2 + 2*x*y + 2*y^2]672673sage: BinaryQF_reduced_representatives(-16)674[x^2 + 4*y^2, 2*x^2 + 2*y^2]675676sage: BinaryQF_reduced_representatives(-63)677[x^2 + x*y + 16*y^2, 2*x^2 - x*y + 8*y^2, 2*x^2 + x*y + 8*y^2, 3*x^2 + 3*x*y + 6*y^2, 4*x^2 + x*y + 4*y^2]678679The number of inequivalent reduced binary forms with a fixed negative680fundamental discriminant D is the class number of the quadratic field681`Q(\sqrt{D})`::682683sage: len(BinaryQF_reduced_representatives(-13*4))6842685sage: QuadraticField(-13*4, 'a').class_number()6862687sage: p=next_prime(2^20); p6881048583689sage: len(BinaryQF_reduced_representatives(-p))690689691sage: QuadraticField(-p, 'a').class_number()692689693694sage: BinaryQF_reduced_representatives(-23*9)695[x^2 + x*y + 52*y^2,6962*x^2 - x*y + 26*y^2,6972*x^2 + x*y + 26*y^2,6983*x^2 + 3*x*y + 18*y^2,6994*x^2 - x*y + 13*y^2,7004*x^2 + x*y + 13*y^2,7016*x^2 - 3*x*y + 9*y^2,7026*x^2 + 3*x*y + 9*y^2,7038*x^2 + 7*x*y + 8*y^2]704sage: BinaryQF_reduced_representatives(-23*9, primitive_only=True)705[x^2 + x*y + 52*y^2,7062*x^2 - x*y + 26*y^2,7072*x^2 + x*y + 26*y^2,7084*x^2 - x*y + 13*y^2,7094*x^2 + x*y + 13*y^2,7108*x^2 + 7*x*y + 8*y^2]711712TESTS::713714sage: BinaryQF_reduced_representatives(5)715Traceback (most recent call last):716...717ValueError: discriminant must be negative and congruent to 0 or 1 modulo 4718"""719D = ZZ(D)720if not ( D < 0 and (D % 4 in [0,1])):721raise ValueError, "discriminant must be negative and congruent to 0 or 1 modulo 4"722723# For a fundamental discriminant all forms are primitive so we need not check:724if primitive_only:725primitive_only = not is_fundamental_discriminant(D)726727form_list = []728729from sage.misc.all import xsrange730from sage.rings.arith import gcd731732# Only iterate over positive a and over b of the same733# parity as D such that 4a^2 + D <= b^2 <= a^2734for a in xsrange(1,1+((-D)//3).isqrt()):735a4 = 4*a736s = D + a*a4737w = 1+(s-1).isqrt() if s > 0 else 0738if w%2 != D%2: w += 1739for b in xsrange(w,a+1,2):740t = b*b-D741if t % a4 == 0:742c = t // a4743if (not primitive_only) or gcd([a,b,c])==1:744if b>0 and a>b and c>a:745form_list.append(BinaryQF([a,-b,c]))746form_list.append(BinaryQF([a,b,c]))747748form_list.sort()749return form_list750751752