Repository: sagemath/sage

Issue:
binary_qf tests fail for a particular random seed
### Is there an existing issue for this?

- [X] I have searched the existing issues for a bug report that matches the one I want to file, without success.

### Did you read the documentation and troubleshoot guide?

- [X] I have read the documentation and troubleshoot guide

### Environment

```markdown
- **Fedora, Ubuntu**:
- **10.0.beta4**:
```


### Steps To Reproduce

See below (Additional info) for an easy reproducer at Sage prompt:

build 10.0.beta4 and run
```
./sage -tp --random-seed=11559027636060728728796865698718805166 src/sage/quadratic_forms/binary_qf.py
```
on #35290 (where I was lucky to get this random seed), and also reproducible on Fedora box, an error as below.
OK with several other seeds.


### Expected Behavior

no error

### Actual Behavior

```
Doctesting 1 file using 8 threads.
sage -t --random-seed=11559027636060728728796865698718805166 src/sage/quadratic_forms/binary_qf.py
**********************************************************************
File "src/sage/quadratic_forms/binary_qf.py", line 1597, in sage.quadratic_forms.binary_qf.BinaryQF.solve_integer
Failed example:
    xy = Q.solve_integer(n)
Exception raised:
    Traceback (most recent call last):
      File "/home/scratch/scratch2/dimpase/sage/sage/src/sage/doctest/forker.py", line 695, in _run
        self.compile_and_execute(example, compiler, test.globs)
      File "/home/scratch/scratch2/dimpase/sage/sage/src/sage/doctest/forker.py", line 1093, in compile_and_execute
        exec(compiled, globs)
      File "<doctest sage.quadratic_forms.binary_qf.BinaryQF.solve_integer[18]>", line 1, in <module>
        xy = Q.solve_integer(n)
      File "/home/scratch/scratch2/dimpase/sage/sage/src/sage/quadratic_forms/binary_qf.py", line 1634, in solve_integer
        y = y_num // Q._b
      File "sage/structure/element.pyx", line 1838, in sage.structure.element.Element.__floordiv__
        return (<Element>left)._floordiv_(right)
      File "sage/rings/integer.pyx", line 2091, in sage.rings.integer.Integer._floordiv_
        raise ZeroDivisionError("Integer division by zero")
    ZeroDivisionError: Integer division by zero
**********************************************************************
File "src/sage/quadratic_forms/binary_qf.py", line 1598, in sage.quadratic_forms.binary_qf.BinaryQF.solve_integer
Failed example:
    Q(*xy) == n
Exception raised:
    Traceback (most recent call last):
      File "/home/scratch/scratch2/dimpase/sage/sage/src/sage/doctest/forker.py", line 695, in _run
        self.compile_and_execute(example, compiler, test.globs)
      File "/home/scratch/scratch2/dimpase/sage/sage/src/sage/doctest/forker.py", line 1093, in compile_and_execute
        exec(compiled, globs)
      File "<doctest sage.quadratic_forms.binary_qf.BinaryQF.solve_integer[19]>", line 1, in <module>
        Q(*xy) == n
    TypeError: 207872*x^2 - 2156672*x*y + 5593868*y^2 argument after * must be an iterable, not NoneType
**********************************************************************
1 item had failures:
   2 of  23 in sage.quadratic_forms.binary_qf.BinaryQF.solve_integer
    [313 tests, 2 failures, 0.21 s]
----------------------------------------------------------------------
sage -t --random-seed=11559027636060728728796865698718805166 src/sage/quadratic_forms/binary_qf.py  # 2 doctests failed
----------------------------------------------------------------------
```


### Additional Information

here is the reproducer without random stuff - obtained by printing values in the test for the
random seed in question:

```
sage: RR.<x,y>=ZZ[]
sage: Q=BinaryQF(207872*x^2 - 2156672*x*y + 5593868*y^2)
sage: Q.solve_integer(812)
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In [10], line 1
----> 1 Q.solve_integer(Integer(812))

File /home/scratch/scratch2/dimpase/sage/sage/src/sage/quadratic_forms/binary_qf.py:1638, in BinaryQF.solve_integer(self, n)
   1636     y_num = n // x - Q._a * x
   1637     if Q._b.divides(y_num):
-> 1638         y = y_num // Q._b
   1639         return tuple(row[0]*x + row[1]*y for row in M.rows())
   1641 return None

File /home/scratch/scratch2/dimpase/sage/sage/src/sage/structure/element.pyx:1838, in sage.structure.element.Element.__floordiv__()
   1836 cdef int cl = classify_elements(left, right)
   1837 if HAVE_SAME_PARENT(cl):
-> 1838     return (<Element>left)._floordiv_(right)
   1839 if BOTH_ARE_ELEMENT(cl):
   1840     return coercion_model.bin_op(left, right, floordiv)

File /home/scratch/scratch2/dimpase/sage/sage/src/sage/rings/integer.pyx:2091, in sage.rings.integer.Integer._floordiv_()
   2089 """
   2090 if not mpz_sgn((<Integer>right).value):
-> 2091     raise ZeroDivisionError("Integer division by zero")
   2092 
   2093 cdef Integer z = <Integer>PY_NEW(Integer)

ZeroDivisionError: Integer division by zero
```


Discussion:

Resolving patch:
diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py
--- a/src/sage/quadratic_forms/binary_qf.py
+++ b/src/sage/quadratic_forms/binary_qf.py
@@ -51,7 +51,7 @@
 # ****************************************************************************
 from functools import total_ordering
 
-from sage.libs.pari.all import pari_gen
+from sage.libs.pari.all import pari_gen, pari
 from sage.rings.integer_ring import ZZ
 from sage.rings.number_field.number_field import is_fundamental_discriminant
 from sage.arith.misc import gcd
@@ -1541,7 +1541,7 @@ def small_prime_value(self, Bmax=1000):
                 raise ValueError("Unable to find a prime value of %s" % self)
             B += 10
 
-    def solve_integer(self, n):
+    def solve_integer(self, n, *, algorithm="general"):
         r"""
         Solve `Q(x, y) = n` in integers `x` and `y` where `Q` is this
         quadratic form.
@@ -1550,12 +1550,18 @@ def solve_integer(self, n):
 
         - ``n`` -- a positive integer
 
+        - ``algorithm`` -- ``"general"`` (default) or ``"cornacchia"``
+
+        To use the Cornacchia algorithm, the quadratic form must have
+        `a=1` and `b=0` and `c>0`, and ``n`` must be a prime or four
+        times a prime (but this is not checked).
+
         OUTPUT:
 
         A tuple `(x, y)` of integers satisfying `Q(x, y) = n`, or ``None``
         if no solution exists.
 
-        ALGORITHM: :pari:`qfbsolve`
+        ALGORITHM: :pari:`qfbsolve` or :pari:`qfbcornacchia`
 
         EXAMPLES::
 
@@ -1563,6 +1569,21 @@ def solve_integer(self, n):
             sage: Q.solve_integer(773187972)
             (4919, 1337)
 
+        If `Q` is of the form `[1,0,c]` as above and `n` is a prime or
+        four times a prime, Cornacchia's algorithm can be used, which is
+        typically much faster than the general method::
+
+            sage: Q = BinaryQF([1, 0, 12345])
+            sage: n = 2^99 + 5273
+            sage: Q.solve_integer(n)
+            (-67446480057659, 7139620553488)
+            sage: Q.solve_integer(n, algorithm='cornacchia')
+            (67446480057659, 7139620553488)
+            sage: timeit('Q.solve_integer(n)')                          # not tested
+            125 loops, best of 3: 3.13 ms per loop
+            sage: timeit('Q.solve_integer(n, algorithm="cornacchia")')  # not tested
+            625 loops, best of 3: 18.6 μs per loop
+
         ::
 
             sage: Qs = BinaryQF_reduced_representatives(-23, primitive_only=True)
@@ -1585,6 +1606,20 @@ def solve_integer(self, n):
             sage: xy is None or Q(*xy) == n
             True
 
+        Also when using the ``"cornacchia"`` algorithm::
+
+            sage: abc = [1,0,randrange(1,10^3)]
+            sage: Q = BinaryQF(abc)
+            sage: n = random_prime(10^9)
+            sage: if randrange(2):
+            ....:     n *= 4
+            sage: xy1 = Q.solve_integer(n, algorithm='cornacchia')
+            sage: xy1 is None or Q(*xy1) == n
+            True
+            sage: xy2 = Q.solve_integer(n)
+            sage: (xy1 is None) == (xy2 is None)
+            True
+
         Test for square discriminants specifically (:trac:`33026`)::
 
             sage: n = randrange(-10^3, 10^3)
@@ -1626,6 +1661,13 @@ def solve_integer(self, n):
             Q = self.matrix_action_right(M)
             assert not Q._c
 
+            if not Q._b:
+                # at this point, Q = a*x^2
+                if Q._a.divides(n) and (n // Q._a).is_square():
+                    x = (n // Q._a).isqrt()
+                    return tuple(row[0] * x for row in M.rows())
+                return None
+
             # at this point, Q = a*x^2 + b*x*y
             if not n:
                 return tuple(M.columns()[1])
@@ -1633,10 +1675,19 @@ def solve_integer(self, n):
                 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 tuple([row[0]*x + row[1]*y for row in M.rows()])
 
             return None
 
+        if algorithm == 'cornacchia':
+            if not (self._a.is_one() and self._b.is_zero() and self._c > 0):
+                raise ValueError("Cornacchia's algorithm requires a=1 and b=0 and c>0")
+            sol = pari.qfbcornacchia(self._c, n)
+            return tuple(map(ZZ, sol)) if sol else None
+
+        if algorithm != 'general':
+            raise ValueError(f'algorithm {algorithm!r} is not a valid algorithm')
+
         flag = 2  # single solution, possibly imprimitive
         sol = self.__pari__().qfbsolve(n, flag)
         return tuple(map(ZZ, sol)) if sol else None


Test patch: