Repository: sagemath/sage

Issue:
Incorrect doctest in `set_order`
These lines in the doctest is incorrect:

https://github.com/sagemath/sage/blob/3dd953c3aa6b5143071b6c39208199cf128c8080/src/sage/schemes/elliptic_curves/ell_finite_field.py#L1346-L1351

The elliptic curve actually has order 12, as shown by printing `E.points()`:

```
sage: E.points()
[(0 : 1 : 0), (0 : 1 : 1), (0 : 6 : 1), (1 : 3 : 1), (1 : 4 : 1), (2 : 3 : 1), (2 : 4 : 1), (3 : 0 : 1), (4 : 3 : 1), (4 : 4 : 1), (5 : 0 : 1), (6 : 0 : 1)]
```

### Additional Information

All points have order $6$, but not all groups are abelian!

Fixed in #37119.



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
@@ -1596,17 +1596,21 @@ 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, *, algorithm="general"):
+    def solve_integer(self, n, *, algorithm="general", _flag=2):
         r"""
         Solve `Q(x, y) = n` in integers `x` and `y` where `Q` is this
         quadratic form.
 
         INPUT:
 
-        - ``n`` -- a positive integer
+        - ``n`` -- a positive integer or a
+          `:sage:`~sage.structure.factorization.Factorization` object
 
         - ``algorithm`` -- ``"general"`` (default) or ``"cornacchia"``
 
+        - ``_flag`` -- ``1``, ``2`` (default) or ``3``; passed onto the pari
+          function``qfbsolve``. For internal use only.
+
         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).
@@ -1618,6 +1622,8 @@ def solve_integer(self, n, *, algorithm="general"):
 
         ALGORITHM: :pari:`qfbsolve` or :pari:`qfbcornacchia`
 
+        TODO:: Replace `_flag` with human-readable parameters c.f. :issue:`37119`
+
         EXAMPLES::
 
             sage: Q = BinaryQF([1, 0, 419])
@@ -1653,6 +1659,14 @@ def solve_integer(self, n, *, algorithm="general"):
             sage: [Q.solve_integer(6) for Q in Qs]
             [(1, -1), (1, -1), (-1, -1)]
 
+        ::
+
+            sage: # needs sage.libs.pari
+            sage: n = factor(126)
+            sage: Q = BinaryQF([1, 0, 5])
+            sage: Q.solve_integer(n)
+            (11, -1)
+
         TESTS:
 
         The returned solutions are correct (random inputs)::
@@ -1702,22 +1716,39 @@ def solve_integer(self, n, *, algorithm="general"):
             sage: Q = Q.matrix_action_right(U)
             sage: Q.discriminant().is_square()
             True
-            sage: xy = Q.solve_integer(n)                                               # needs sage.libs.pari
-            sage: Q(*xy) == n                                                           # needs sage.libs.pari
+            sage: # needs sage.libs.pari
+            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)                                               # needs sage.libs.pari
-            sage: Q(*xy)                                                                # needs sage.libs.pari
+            sage: # needs sage.libs.pari
+            sage: xy = Q.solve_integer(0)
+            sage: Q(*xy)
             0
-        """
-        n = ZZ(n)
 
+        Test for different `_flag` values::
+
+            sage: # needs sage.libs.pari
+            sage: Q = BinaryQF([1, 0, 5])
+            sage: Q.solve_integer(126, _flag=1)
+            [(11, -1), (-1, -5), (-1, 5), (-11, -1)]
+            sage: Q.solve_integer(126, _flag=2)
+            (11, -1)
+            sage: Q.solve_integer(126, _flag=3)
+            [(11, -1), (-1, -5), (-1, 5), (-11, -1), (-9, -3), (9, -3)]
+        """
         if self.is_negative_definite():  # not supported by PARI
             return (-self).solve_integer(-n)
 
         if self.is_reducible():  # square discriminant; not supported by PARI
+            from sage.structure.factorization import Factorization
+            if isinstance(n, Factorization):
+                n = ZZ(n.value())
+            else:
+                n = ZZ(n)
+
             if self._a:
                 # https://math.stackexchange.com/a/980075
                 w = self.discriminant().sqrt()
@@ -1760,9 +1791,10 @@ def solve_integer(self, n, *, algorithm="general"):
         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
+        sol = self.__pari__().qfbsolve(n, _flag)
+        if _flag == 2:
+            return tuple(map(ZZ, sol)) if sol else None
+        return list(map(lambda tup: tuple(map(ZZ, tup)), sol))
 
     def form_class(self):
         r"""
diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py
--- a/src/sage/schemes/elliptic_curves/ell_finite_field.py
+++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py
@@ -1357,22 +1357,18 @@ def set_order(self, value, *, check=True, num_checks=8):
 
         EXAMPLES:
 
-        This example illustrates basic usage.
+        This example illustrates basic usage::
 
-        ::
-
-            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6
-            sage: E.set_order(6)
+            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12
+            sage: E.set_order(12)
             sage: E.order()
-            6
+            12
             sage: E.order() * E.random_point()
             (0 : 1 : 0)
 
         We now give a more interesting case, the NIST-P521 curve. Its
         order is too big to calculate with Sage, and takes a long time
-        using other packages, so it is very useful here.
-
-        ::
+        using other packages, so it is very useful here::
 
             sage: p = 2^521 - 1
             sage: prev_proof_state = proof.arithmetic()
@@ -1391,7 +1387,7 @@ def set_order(self, value, *, check=True, num_checks=8):
         It is an error to pass a value which is not an integer in the
         Hasse-Weil range::
 
-            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6
+            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12
             sage: E.set_order("hi")
             Traceback (most recent call last):
             ...
@@ -1410,40 +1406,50 @@ def set_order(self, value, *, check=True, num_checks=8):
         ``num_checks``, the factorization of the actual order, and the
         actual group structure::
 
-            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6
+            sage: E = EllipticCurve(GF(1009), [0, 1]) # This curve has order 948
+            sage: E.set_order(947)
+            Traceback (most recent call last):
+            ...
+            ValueError: Value 947 illegal (multiple of random point not the identity)
+
+        For curves over small finite fields, the order is cheap to compute, so it is computed
+        directly and compared::
+
+            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12
             sage: E.set_order(11)
             Traceback (most recent call last):
             ...
-            ValueError: Value 11 illegal (multiple of random point not the identity)
+            ValueError: Value 11 illegal (correct order is 12)
 
-        However, set_order can be fooled, though it's not likely in
-        "real cases of interest". For instance, the order can be set
-        to a multiple of the actual order::
+        TESTS:
 
-            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6
-            sage: E.set_order(12)  # 12 just fits in the Hasse range
-            sage: E.order()
-            12
+        The previous version's random tests are not strong enough. In particular, the following used
+        to work::
+
+            sage: E = EllipticCurve(GF(2), [0, 0, 1, 1, 1]) # This curve has order 1
+            sage: E.set_order(3)
+            Traceback (most recent call last):
+            ...
+            ValueError: Value 3 illegal (correct order is 1)
 
-        Or, the order can be set incorrectly along with ``num_checks`` set
-        too small::
+        ::
 
-            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6
+            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 12
             sage: E.set_order(4, num_checks=0)
+            Traceback (most recent call last):
+            ...
+            ValueError: Value 4 illegal (correct order is 12)
             sage: E.order()
-            4
-
-        The value of ``num_checks`` must be an integer. Negative values
-        are interpreted as zero, which means don't do any checking::
+            12
 
-            sage: E = EllipticCurve(GF(7), [0, 1]) # This curve has order 6
-            sage: E.set_order(4, num_checks=-12)
-            sage: E.order()
-            4
+        .. TODO:: Add provable correctness check by computing the abelian group structure and
+            comparing.
 
         AUTHORS:
 
-         - Mariah Lenox (2011-02-16)
+         - Mariah Lenox (2011-02-16): Initial implementation
+
+         - Gareth Ma (2024-01-21): Fix bug for small curves
         """
         value = Integer(value)
 
@@ -1452,12 +1458,19 @@ def set_order(self, value, *, check=True, num_checks=8):
             q = self.base_field().order()
             a,b = Hasse_bounds(q,1)
             if not a <= value <= b:
-                raise ValueError('Value %s illegal (not an integer in the Hasse range)' % value)
+                raise ValueError(f"Value {value} illegal (not an integer in the Hasse range)")
+
+            # For really small values, the random tests are too weak to detect wrong orders
+            # So we go with computing directly instead.
+            if q <= 100:
+                if self.order() != value:
+                    raise ValueError(f"Value {value} illegal (correct order is {self.order()})")
+
             # Is value*random == identity?
-            for i in range(num_checks):
+            for _ in range(num_checks):
                 G = self.random_point()
                 if value * G != self(0):
-                    raise ValueError('Value %s illegal (multiple of random point not the identity)' % value)
+                    raise ValueError(f"Value {value} illegal (multiple of random point not the identity)")
 
         # TODO: It might help some of PARI's algorithms if we
         # could copy this over to the .pari_curve() as well.
@@ -1552,10 +1565,9 @@ def height_above_floor(self, ell, e):
         F = j.parent()
         x = polygen(F)
         from sage.rings.polynomial.polynomial_ring import polygens
-        from sage.libs.pari.convert_sage import gen_to_sage
-        from sage.libs.pari.all import pari
-        X,Y = polygens(F,['X', 'Y'],2)
-        phi = gen_to_sage(pari.polmodular(ell),{'x':X, 'y':Y})
+        from sage.schemes.elliptic_curves.mod_poly import classical_modular_polynomial
+        X, Y = polygens(F, "X, Y", 2)
+        phi = classical_modular_polynomial(ell)(X, Y)
         j1 = phi([x,j]).roots(multiplicities=False)
         nj1 = len(j1)
         on_floor = self.two_torsion_rank() < 2 if ell == 2 else nj1 <= ell
@@ -1824,9 +1836,10 @@ def twists(self):
             twists = curves_with_j_0(K)
         elif j == 1728:
             twists = curves_with_j_1728(K)
-        if twists: # i.e. if j=0 or 1728
+
+        if twists:  # i.e. if j=0 or 1728
             # replace the one isomorphic to self with self and move to front
-            for i,t in enumerate(twists):
+            for i, t in enumerate(twists):
                 if self.is_isomorphic(t):
                     twists[i] = twists[0]
                     twists[0] = self
@@ -1835,15 +1848,20 @@ def twists(self):
 
         # Now j is not 0 or 1728, and we only have a quadratic twist
 
-        if K.characteristic() == 2: # find D with trace 1 for the additive twist
-            D = K.one() # will work if degree is odd
-            while D.trace() == 0:
+        if K.characteristic() == 2:
+            # find D with trace 1 for the additive twist
+            D = K.one()
+            while not D or D.trace() == 0:
                 D = K.random_element()
-        else: # find a nonsquare D
+        else:
+            # find a nonsquare D.
             D = K.gen()
-            q2 = (K.cardinality()-1)//2
+            q2 = (K.cardinality() - 1) // 2
             while not D or D**q2 == 1:
                 D = K.random_element()
+            # assert D and D**q2 != 1
+            # assert not D.is_square()
+
         return [self, self.quadratic_twist(D)]
 
 def curves_with_j_0(K):
@@ -1860,6 +1878,7 @@ def curves_with_j_0(K):
 
     For `K=\GF{q}` where `q\equiv1\mod{6}` there are six curves, the sextic twists of `y^2=x^3+1`::
 
+        sage: # needs sage.rings.finite_rings
         sage: from sage.schemes.elliptic_curves.ell_finite_field import curves_with_j_0
         sage: sorted(curves_with_j_0(GF(7)), key = lambda E: E.a_invariants())
         [Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field of size 7,
@@ -1868,13 +1887,12 @@ def curves_with_j_0(K):
          Elliptic Curve defined by y^2 = x^3 + 4 over Finite Field of size 7,
          Elliptic Curve defined by y^2 = x^3 + 5 over Finite Field of size 7,
          Elliptic Curve defined by y^2 = x^3 + 6 over Finite Field of size 7]
-        sage: curves_with_j_0(GF(25))                                                   # needs sage.rings.finite_rings
-        [Elliptic Curve defined by y^2 = x^3 + 1 over Finite Field in z2 of size 5^2,
-         Elliptic Curve defined by y^2 = x^3 + z2 over Finite Field in z2 of size 5^2,
-         Elliptic Curve defined by y^2 = x^3 + (z2+3) over Finite Field in z2 of size 5^2,
-         Elliptic Curve defined by y^2 = x^3 + (4*z2+3) over Finite Field in z2 of size 5^2,
-         Elliptic Curve defined by y^2 = x^3 + (2*z2+2) over Finite Field in z2 of size 5^2,
-         Elliptic Curve defined by y^2 = x^3 + (4*z2+1) over Finite Field in z2 of size 5^2]
+        sage: curves = curves_with_j_0(GF(25)); len(curves)
+        6
+        sage: all(not curves[i].is_isomorphic(curves[j]) for i in range(6) for j in range(i + 1, 6))
+        True
+        sage: set(E.j_invariant() for E in curves)
+        {0}
 
     For `K=\GF{q}` where `q\equiv5\mod{6}` there are two curves,
     quadratic twists of each other by `-3`: `y^2=x^3+1` and
@@ -1898,14 +1916,22 @@ def curves_with_j_0(K):
     q = K.cardinality()
     if q % 3 == 2:
         # Then we only have two quadratic twists (and -3 is non-square)
-        return [EllipticCurve(K, [0,a]) for a in [1,-27]]
+        return [EllipticCurve(K, [0, a]) for a in [1, -27]]
     # Now we have genuine sextic twists, find D generating K* mod 6th powers
-    q2 = (q-1)//2
-    q3 = (q-1)//3
+    q2 = (q - 1) // 2
+    q3 = (q - 1) // 3
     D = K.gen()
     while not D or D**q2 == 1 or D**q3 == 1:
         D = K.random_element()
-    return [EllipticCurve(K, [0,D**i]) for i in range(6)]
+
+    curves = [EllipticCurve(K, [0, D**i]) for i in range(6)]
+    # TODO: issue 37110, Precompute orders of sextic twists + docs
+    # The idea should be to evaluate the character (D / q) or something
+    # Probably reference [RS2010]_ and [Connell1999]_
+    # Also a necessary change is `curves_with_j_0` should take in an optional "starting curve"
+    # (passed from the original .twists call), because if you start twisting from that curve,
+    # then you can also compute the orders!
+    return curves
 
 def curves_with_j_1728(K):
     r"""
@@ -1927,7 +1953,7 @@ def curves_with_j_1728(K):
          Elliptic Curve defined by y^2 = x^3 + 2*x over Finite Field of size 5,
          Elliptic Curve defined by y^2 = x^3 + 3*x over Finite Field of size 5,
          Elliptic Curve defined by y^2 = x^3 + 4*x over Finite Field of size 5]
-        sage: curves_with_j_1728(GF(49))                                                # needs sage.rings.finite_rings
+        sage: curves_with_j_1728(GF(49))  # random                                      # needs sage.rings.finite_rings
         [Elliptic Curve defined by y^2 = x^3 + x over Finite Field in z2 of size 7^2,
          Elliptic Curve defined by y^2 = x^3 + z2*x over Finite Field in z2 of size 7^2,
          Elliptic Curve defined by y^2 = x^3 + (z2+4)*x over Finite Field in z2 of size 7^2,
@@ -1956,11 +1982,12 @@ def curves_with_j_1728(K):
     if q % 4 == 3:
         return [EllipticCurve(K, [a,0]) for a in [1,-1]]
     # Now we have genuine quartic twists, find D generating K* mod 4th powers
-    q2 = (q-1)//2
+    q2 = (q - 1) // 2
     D = K.gen()
     while not D or D**q2 == 1:
         D = K.random_element()
-    return [EllipticCurve(K, [D**i,0]) for i in range(4)]
+    curves = [EllipticCurve(K, [D**i, 0]) for i in range(4)]
+    return curves
 
 def curves_with_j_0_char2(K):
     r"""
@@ -2632,3 +2659,119 @@ def special_supersingular_curve(F, *, endomorphism=False):
     endo._degree = ZZ(q)
     endo.trace.set_cache(ZZ.zero())
     return E, endo
+
+def EllipticCurve_with_order(m, *, D=None):
+    r"""
+    Return an iterator for elliptic curves over finite fields with the given order. The curves are
+    computed using the Complex Multiplication (CM) method.
+
+    A `:sage:`~sage.structure.factorization.Factorization` can be passed for ``m``, in which case
+    the algorithm is more efficient.
+
+    If ``D`` is specified, it is used as the discriminant.
+
+    EXAMPLES::
+
+        sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_with_order
+        sage: E = next(EllipticCurve_with_order(1234)); E  # random
+        Elliptic Curve defined by y^2 = x^3 + 1142*x + 1209 over Finite Field of size 1237
+        sage: E.order() == 1234
+        True
+
+    When ``iter`` is set, the function returns an iterator of all elliptic curves with the given
+    order::
+
+        sage: from sage.schemes.elliptic_curves.ell_finite_field import EllipticCurve_with_order
+        sage: it = EllipticCurve_with_order(21); it
+        <generator object EllipticCurve_with_order at 0x...>
+        sage: E = next(it); E  # random
+        Elliptic Curve defined by y^2 = x^3 + 6*x + 14 over Finite Field of size 23
+        sage: E.order() == 21
+        True
+        sage: Es = [E] + list(it); Es  # random
+        [Elliptic Curve defined by y^2 = x^3 + 6*x + 14 over Finite Field of size 23,
+         Elliptic Curve defined by y^2 = x^3 + 12*x + 4 over Finite Field of size 23,
+         Elliptic Curve defined by y^2 = x^3 + 5*x + 2 over Finite Field of size 23,
+         Elliptic Curve defined by y^2 = x^3 + (z2+3) over Finite Field in z2 of size 5^2,
+         Elliptic Curve defined by y^2 = x^3 + (2*z2+2) over Finite Field in z2 of size 5^2,
+         Elliptic Curve defined by y^2 = x^3 + 7*x + 1 over Finite Field of size 19,
+         Elliptic Curve defined by y^2 = x^3 + 17*x + 10 over Finite Field of size 19,
+         Elliptic Curve defined by y^2 = x^3 + 5*x + 12 over Finite Field of size 17,
+         Elliptic Curve defined by y^2 = x^3 + 9*x + 1 over Finite Field of size 17,
+         Elliptic Curve defined by y^2 = x^3 + 7*x + 6 over Finite Field of size 17,
+         Elliptic Curve defined by y^2 = x^3 + z3^2*x^2 + (2*z3^2+z3) over Finite Field in z3 of size 3^3,
+         Elliptic Curve defined by y^2 = x^3 + (z3^2+2*z3+1)*x^2 + (2*z3^2+2*z3) over Finite Field in z3 of size 3^3,
+         Elliptic Curve defined by y^2 = x^3 + (z3^2+z3+1)*x^2 + (2*z3^2+1) over Finite Field in z3 of size 3^3,
+         Elliptic Curve defined by y^2 + (z4^2+z4+1)*y = x^3 over Finite Field in z4 of size 2^4,
+         Elliptic Curve defined by y^2 + (z4^2+z4)*y = x^3 over Finite Field in z4 of size 2^4,
+         Elliptic Curve defined by y^2 = x^3 + 18*x + 26 over Finite Field of size 29,
+         Elliptic Curve defined by y^2 = x^3 + 11*x + 19 over Finite Field of size 29,
+         Elliptic Curve defined by y^2 = x^3 + 4 over Finite Field of size 19,
+         Elliptic Curve defined by y^2 = x^3 + 19 over Finite Field of size 31,
+         Elliptic Curve defined by y^2 = x^3 + 4 over Finite Field of size 13]
+        sage: all(E.order() == 21 for E in Es)
+        True
+
+    Indeed, we can verify that this is correct. Hasse's bounds tell us that $p \leq 50$
+    (approximately), and the rest can be checked via bruteforce::
+
+        sage: for p in prime_range(50):
+        ....:     for j in range(p):
+        ....:         E0 = EllipticCurve(GF(p), j=j)
+        ....:         for Et in E0.twists():
+        ....:             if Et.order() == 21:
+        ....:                 assert any(Et.is_isomorphic(E) for E in Es)
+
+    .. NOTE::
+
+        The output curves are not deterministic, as :func:`EllipticCurve_finite_field.twists` is not
+        deterministic. However, the order of the j-invariants and base fields is fixed.
+
+    AUTHORS:
+
+     - Gareth Ma and Giacomo Pope (Sage Days 123): initial version
+    """
+    from sage.arith.misc import is_prime_power, factor
+    from sage.quadratic_forms.binary_qf import BinaryQF
+    from sage.structure.factorization import Factorization
+    from sage.schemes.elliptic_curves.cm import hilbert_class_polynomial
+
+    def find_q(m, m4_fac, D):
+        for t, _ in BinaryQF(1, 0, -D).solve_integer(m4_fac, _flag=3):
+            yield m + 1 - t
+            yield m + 1 + t
+
+    if isinstance(m, Factorization):
+        m4_fac = m * factor(4)
+        m_val = m.value()
+    else:
+        m4_fac = factor(m * 4)
+        m_val = m
+
+    if D is None:
+        Ds = (D for D in range(-4 * m_val, 0) if D % 4 in [0, 1])
+    else:
+        assert D < 0 and D % 4 in [0, 1]
+        Ds = [D]
+
+    seen = set()
+    for D in Ds:
+        for q in find_q(m_val, m4_fac, D):
+            if not is_prime_power(q):
+                continue
+
+            H = hilbert_class_polynomial(D)
+            K = GF(q)
+            roots = H.roots(ring=K)
+            for j0, _ in roots:
+                E = EllipticCurve(j=j0)
+                for Et in E.twists():
+                    if any(Et.is_isomorphic(E) for E in seen):
+                        continue
+                    try:
+                        # This tests whether the curve has given order
+                        Et.set_order(m_val)
+                        seen.add(Et)
+                        yield Et
+                    except ValueError:
+                        pass


Test patch: