Skip to content

Commit

Permalink
Trac #33026: make BinaryQF.solve_integer() work for square discriminants
Browse files Browse the repository at this point in the history
As reported in comment:8:ticket:32782, PARI's `qfbsolve()` does not work
with quadratic forms whose discriminant is a square integer.

This currently triggers failures in random testing, but it is not a
regression: `BinaryQF.solve_integer()` used to reject ''all'' positive
discriminants prior to #32782.

URL: https://trac.sagemath.org/33026
Reported by: lorenz
Ticket author(s): Lorenz Panny
Reviewer(s): Vincent Delecroix
  • Loading branch information
Release Manager committed Feb 12, 2022
2 parents 2ad7305 + 5e8a2a3 commit 236856d
Showing 1 changed file with 48 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/sage/quadratic_forms/binary_qf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1497,11 +1497,59 @@ def solve_integer(self, n):
sage: xy = Q.solve_integer(n)
sage: xy is None or Q(*xy) == n
True
Test for square discriminants specifically (:trac:`33026`)::
sage: n = randrange(-10^3, 10^3)
sage: Q = BinaryQF([n, randrange(-10^3, 10^3), 0][::(-1)**randrange(2)])
sage: U = random_matrix(ZZ, 2, 2, 'unimodular')
sage: U.rescale_row(0, choice((+1,-1)))
sage: assert U.det() in (+1,-1)
sage: Q = Q.matrix_action_right(U)
sage: Q.discriminant().is_square()
True
sage: xy = Q.solve_integer(n)
sage: Q(*xy) == n
True
Also test the `n=0` special case separately::
sage: xy = Q.solve_integer(0)
sage: Q(*xy)
0
"""
n = ZZ(n)

if self.is_negative_definite(): # not supported by PARI
return (-self).solve_integer(-n)

if self.is_reducible(): # square discriminant; not supported by PARI
if self._a:
# https://math.stackexchange.com/a/980075
w = self.discriminant().sqrt()
r = (-self._b + (w if w != self._b else -w)) / (2*self._a)
p,q = r.as_integer_ratio()
g,u,v = p.xgcd(q)
M = Matrix(ZZ, [[v,p],[-u,q]])
elif self._c:
M = Matrix(ZZ, [[0,1],[1,0]])
else:
M = Matrix(ZZ, [[1,0],[0,1]])
assert M.is_unit()
Q = self.matrix_action_right(M)
assert not Q._c

# at this point, Q = a*x^2 + b*x*y
if not n:
return tuple(M.columns()[1])
for x in n.divisors():
y_num = n // x - Q._a * x
if Q._b.divides(y_num):
y = y_num // Q._b
return tuple(row[0]*x + row[1]*y for row in M.rows())

return None

flag = 2 # single solution, possibly imprimitive
sol = self.__pari__().qfbsolve(n, flag)
return tuple(map(ZZ, sol)) if sol else None
Expand Down

0 comments on commit 236856d

Please sign in to comment.