diff --git a/src/sage/categories/commutative_rings.py b/src/sage/categories/commutative_rings.py index 23347e44f1a..693c159740f 100644 --- a/src/sage/categories/commutative_rings.py +++ b/src/sage/categories/commutative_rings.py @@ -576,7 +576,181 @@ def _pseudo_fraction_field(self): return coercion_model.division_parent(self) class ElementMethods: - pass + def is_square(self, root=False): + """ + Return whether or not the ring element ``self`` is a square. + If the optional argument root is ``True``, then also return + the square root (or ``None``, if it is not a square). + + INPUT: + + - ``root`` -- boolean (default: ``False``); whether or not to also + return a square root + + OUTPUT: + + - boolean; whether or not a square + + - object; (optional) an actual square root if found, and ``None`` + otherwise + + EXAMPLES:: + + sage: R. = PolynomialRing(QQ) + sage: f = 12*(x+1)^2 * (x+3)^2 + sage: f.is_square() + False + sage: f.is_square(root=True) + (False, None) + sage: h = f/3 + sage: h.is_square() + True + sage: h.is_square(root=True) + (True, 2*x^2 + 8*x + 6) + + .. NOTE:: + + This is the is_square implementation for general commutative ring + elements. It's implementation is to raise a + :exc:`NotImplementedError`. The function definition is here to show + what functionality is expected and provide a general framework. + """ + raise NotImplementedError("is_square() not implemented for elements of %s" % self.parent()) + + def sqrt(self, extend=True, all=False, name=None): + """ + Compute the square root. + + INPUT: + + - ``extend`` -- boolean (default: ``True``); whether to make a ring + extension containing a square root if ``self`` is not a square + + - ``all`` -- boolean (default: ``False``); whether to return a list of + all square roots or just a square root + + - ``name`` -- required when ``extend=True`` and ``self`` is not a + square; this will be the name of the generator of the extension + + OUTPUT: + + - if ``all=False``, a square root; raises an error if ``extend=False`` + and ``self`` is not a square + + - if ``all=True``, a list of all the square roots (empty if + ``extend=False`` and ``self`` is not a square) + + ALGORITHM: + + It uses ``is_square(root=true)`` for the hard part of the work, the rest + is just wrapper code. + + EXAMPLES:: + + sage: # needs sage.libs.pari + sage: R. = ZZ[] + sage: (x^2).sqrt() + x + sage: f = x^2 - 4*x + 4; f.sqrt(all=True) + [x - 2, -x + 2] + sage: sqrtx = x.sqrt(name='y'); sqrtx + y + sage: sqrtx^2 + x + sage: x.sqrt(all=true, name='y') + [y, -y] + sage: x.sqrt(extend=False, all=True) + [] + sage: x.sqrt() + Traceback (most recent call last): + ... + TypeError: Polynomial is not a square. You must specify the name + of the square root when using the default extend = True + sage: x.sqrt(extend=False) + Traceback (most recent call last): + ... + ValueError: trying to take square root of non-square x with extend = False + + TESTS:: + + sage: # needs sage.libs.pari + sage: f = (x + 3)^2; f.sqrt() + x + 3 + sage: f = (x + 3)^2; f.sqrt(all=True) + [x + 3, -x - 3] + sage: f = (x^2 - x + 3)^2; f.sqrt() + x^2 - x + 3 + sage: f = (x^2 - x + 3)^6; f.sqrt() + x^6 - 3*x^5 + 12*x^4 - 19*x^3 + 36*x^2 - 27*x + 27 + sage: g = (R.random_element(15))^2 + sage: g.sqrt()^2 == g + True + + sage: # needs sage.libs.pari + sage: R. = GF(250037)[] + sage: f = x^2/(x+1)^2; f.sqrt() + x/(x + 1) + sage: f = 9 * x^4 / (x+1)^2; f.sqrt() + 3*x^2/(x + 1) + sage: f = 9 * x^4 / (x+1)^2; f.sqrt(all=True) + [3*x^2/(x + 1), 250034*x^2/(x + 1)] + + sage: R. = QQ[] + sage: a = 2*(x+1)^2 / (2*(x-1)^2); a.sqrt() + (x + 1)/(x - 1) + sage: sqrtx = (1/x).sqrt(name='y'); sqrtx + y + sage: sqrtx^2 + 1/x + sage: (1/x).sqrt(all=true, name='y') + [y, -y] + sage: (1/x).sqrt(extend=False, all=True) + [] + sage: (1/(x^2-1)).sqrt() + Traceback (most recent call last): + ... + TypeError: Polynomial is not a square. You must specify the name + of the square root when using the default extend = True + sage: (1/(x^2-3)).sqrt(extend=False) + Traceback (most recent call last): + ... + ValueError: trying to take square root of non-square 1/(x^2 - 3) with extend = False + """ + # This code is very general, it works for all integral domains that have the + # is_square(root = True) option + + from sage.categories.integral_domains import IntegralDomains + P = self.parent() + is_sqr, sq_rt = self.is_square(root=True) + if is_sqr: + if all: + if P not in IntegralDomains(): + raise NotImplementedError('sqrt() with all=True is only implemented for integral domains, not for %s' % P) + if P.characteristic() == 2 or sq_rt == 0: + # 0 has only one square root, and in characteristic 2 everything also has only 1 root + return [sq_rt] + return [sq_rt, -sq_rt] + return sq_rt + # from now on we know that self is not a square + if P not in IntegralDomains(): + raise NotImplementedError('sqrt() of non squares is only implemented for integral domains, not for %s' % P) + if not extend: + # all square roots of a non-square should be an empty list + if all: + return [] + raise ValueError('trying to take square root of non-square %s with extend = False' % self) + + if name is None: + raise TypeError("Polynomial is not a square. You must specify the name of the square root when using the default extend = True") + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + PY = PolynomialRing(P, 'y') + y = PY.gen() + sq_rt = PY.quotient(y**2-self, names=name)(y) + if all: + if P.characteristic() == 2: + return [sq_rt] + return [sq_rt, -sq_rt] + return sq_rt class Finite(CategoryWithAxiom): r""" diff --git a/src/sage/categories/finite_fields.py b/src/sage/categories/finite_fields.py index aadfa76286a..666f0a8d828 100644 --- a/src/sage/categories/finite_fields.py +++ b/src/sage/categories/finite_fields.py @@ -7,6 +7,7 @@ # William Stein # 2008 Teresa Gomez-Diaz (CNRS) # 2008-2009 Nicolas M. Thiery +# 2025 Brian Heckel # # Distributed under the terms of the GNU General Public License (GPL) # https://www.gnu.org/licenses/ @@ -15,6 +16,8 @@ from sage.categories.category_with_axiom import CategoryWithAxiom from sage.categories.enumerated_sets import EnumeratedSets from sage.rings.integer import Integer +from sage.rings.integer_ring import ZZ +from sage.misc.cachefunc import cached_method class FiniteFields(CategoryWithAxiom): @@ -243,5 +246,235 @@ def _element_of_factored_order(self, F): return a raise AssertionError("no element found") + @cached_method + def quadratic_nonresidue(self): + r""" + Return a random non square element of the finite field + + OUTPUT: + A non-square element of the finite field; raises an error if + the finite field is of even order. + + EXAMPLES:: + + sage: k = GF((3, 10)) + sage: k.quadratic_nonresidue().is_square() + False + sage: k = GF((2, 10)) + sage: k in Fields() # to let k be a finite field + True + sage: k.quadratic_nonresidue() + Traceback (most recent call last): + ... + ValueError: there are no non-squares in finite fields of even order + """ + # if the order is an even power of two + # then every element is a square + if self.characteristic() == 2: + raise ValueError("there are no non-squares in finite fields of even order") + for element in self: + if not element.is_square(): + return element + class ElementMethods: - pass + def is_square(self) -> bool: + r""" + Test if the element is a square or has + a square root element. + + OUTPUT: + ``True`` if the element is a square ``False`` if not + + EXAMPLES:: + + sage: S. = GF(5)[] + sage: f = S.irreducible_element(20) + sage: k. = S.quotient_ring(f) + sage: k in Fields() + True + sage: k(2).is_square() + True + sage: k.quadratic_nonresidue().is_square() + False + """ + if self.is_zero(): + return True + if self.parent().characteristic() == 2: + return True + q = self.parent().order() + character = self**((q-1)//2) + is_square = character == self.parent().one() + return is_square + + def _tonelli(self): + r""" + Return a square root of the element if it exists + using Tonelli's algorithm, only works for finite fields + of odd characteristic. + + OUTPUT: + A square root of the element; raises an error + if the element is not a square + + EXAMPLES:: + + sage: k. = GF((5, 10)) + sage: k(2).is_square() + True + sage: k(2)._tonelli()^2 == k(2) + True + sage: k.quadratic_nonresidue()._tonelli() + Traceback (most recent call last): + ... + ValueError: element is not a square + """ + q = self.parent().cardinality() + if not self.is_square(): + raise ValueError("element is not a square") + g = self.parent().quadratic_nonresidue() + even_exp, odd_order = (q - ZZ.one()).val_unit(2) + e = 0 + for i in range(2, even_exp+1): + tmp = self * (pow(g, -e)) + + condition = tmp**((q-1)//(2**i)) != self.parent().one() + if condition: + e = 2**(i-1) + e + h = self * (g**(-e)) + b = g**(e//2) * h**((odd_order+1)//2) + return b + + def _cipolla(self): + r""" + Return a square root of the element if it exists + using Cipolla's algorithm, more suited if order - 1 + is highly divisible by 2. Only works for finite fields + of odd characteristic. + + OUTPUT: + A square root of the element; raises an error + if the element is not a square + + EXAMPLES:: + + sage: k. = GF((5, 10)) + sage: k(2).is_square() + True + sage: k(2)._cipolla()^2 == k(2) + True + sage: k.quadratic_nonresidue()._cipolla() + Traceback (most recent call last): + ... + ValueError: element is not a square + """ + parent = self.parent() + q = parent.cardinality() + if not self.is_square(): + raise ValueError("element is not a square") + t = parent.random_element() + root = t**2 - 4 * self + while root.is_square(): + t = parent.random_element() + root = t**2 - 4 * self + from sage.rings.polynomial.polynomial_ring import polygen + X = polygen(parent) + f = X**2 - t*X + self + b = pow(X, (q+1)//2, f) + return b + + def sqrt(self, all: bool = False, algorithm: str = 'tonelli'): + r""" + Return the square root of the element if it exists. + + INPUT: + + - ``all`` -- boolean (default: ``False``); whether to return a list of + all square roots or just a square root + + - ``algorithm`` -- string (default: 'tonelli'); the algorithm to use + among ``'tonelli'``, ``'cipolla'``. Tonelli is typically faster but has + a worse worst-case complexity than Cipolla. In particular, if the + field cardinality minus 1 is highly divisible by 2 and has a large + odd factor then Cipolla may perform better. + + OUTPUT: + + - if ``all=False``, a square root; raises an error if the element is not + a square + + - if ``all=True``, a tuple of all distinct square roots. This tuple can have + length 0, 1, or 2 depending on how many distinct square roots the + element has. + + EXAMPLES:: + + sage: S. = GF(5)[] + sage: f = S.irreducible_element(20) + sage: k. = S.quotient_ring(f) + sage: k in Fields() + True + sage: k(2).is_square() + True + sage: k(2).sqrt()^2 == k(2) + True + sage: my_sqrts = k(4).sqrt(all=True) + sage: len(k(4).sqrt(all=True)) + 2 + sage: 2 in my_sqrts + True + sage: 3 in my_sqrts + True + sage: k.quadratic_nonresidue().sqrt() + Traceback (most recent call last): + ... + ValueError: element is not a square + sage: k.quadratic_nonresidue().sqrt(all=True) + () + + Here is an example where changing the algorithm results + in a faster square root:: + + sage: p = 141 * 2^141 + 1 + sage: S. = GF(p)[] + sage: f = S.irreducible_element(2) + sage: k. = S.quotient_ring(f) + sage: k in Fields() + True + sage: k(2).sqrt(algorithm="cipolla")^2 == k(2) + True + + ALGORITHM: + + The algorithms used come from chapter 7 of [BS1996]_. + Let `q = p^n` be the order of the finite field, let `a` be the finite field element + that we wish to find the square root of. + + - If `p = 2` then `a` is always a square, and the square root of `\sqrt{a} = a^{q / 2}`. + - If `q \equiv 3 \pmod{4}` then if `a` is a square `\sqrt{a} = a^{\frac{q+1}{4}}` + - For all other cases we use the algorithm given by the ``algorithm`` parameter. + """ + cardinality = self.parent().order() + if self.parent().characteristic() == 2: + exponent = cardinality // 2 + square_root = self**exponent + if all: + # we return a 1-tuple because the GF implementation does it + return (square_root,) + else: + return square_root + if not self.is_square(): + if all: + return () + else: + raise ValueError("element is not a square") + if cardinality % 4 == 3: + square_root = self**((cardinality+1)//4) + elif algorithm == 'tonelli': + square_root = self._tonelli() + else: + square_root = self._cipolla() + if all: + return (square_root, -square_root) + return square_root + + square_root = sqrt diff --git a/src/sage/rings/polynomial/polynomial_quotient_ring.py b/src/sage/rings/polynomial/polynomial_quotient_ring.py index 627034877f3..c5c644b9335 100644 --- a/src/sage/rings/polynomial/polynomial_quotient_ring.py +++ b/src/sage/rings/polynomial/polynomial_quotient_ring.py @@ -338,8 +338,16 @@ class of the category, and store the current class of the quotient sage: isinstance(Q.an_element(), Q.element_class) True sage: [s for s in dir(Q.category().element_class) if not s.startswith('_')] - ['cartesian_product', 'inverse', 'inverse_of_unit', 'is_idempotent', - 'is_one', 'is_unit', 'lift', 'powers'] + ['cartesian_product', + 'inverse', + 'inverse_of_unit', + 'is_idempotent', + 'is_one', + 'is_square', + 'is_unit', + 'lift', + 'powers', + 'sqrt'] sage: first_class = Q.__class__ We try to find out whether `Q` is a field. Indeed it is, and thus its category, @@ -361,12 +369,14 @@ class of the category, and store the current class of the quotient 'inverse_of_unit', 'is_idempotent', 'is_one', + 'is_square', 'is_unit', 'lcm', 'lift', 'powers', 'quo_rem', 'radical', + 'sqrt', 'squarefree_part', 'xgcd'] diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 0cd9bcf074e..94a63247806 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -3214,186 +3214,6 @@ cdef class CommutativeRingElement(RingElement): I = self._parent.ideal(I) return I.reduce(self) - ################################################## - # square roots - ################################################## - - def is_square(self, root=False): - """ - Return whether or not the ring element ``self`` is a square. - - If the optional argument root is ``True``, then also return - the square root (or ``None``, if it is not a square). - - INPUT: - - - ``root`` -- boolean (default: ``False``); whether or not to also - return a square root - - OUTPUT: - - - boolean; whether or not a square - - - object; (optional) an actual square root if found, and ``None`` - otherwise - - EXAMPLES:: - - sage: R. = PolynomialRing(QQ) - sage: f = 12*(x+1)^2 * (x+3)^2 - sage: f.is_square() - False - sage: f.is_square(root=True) - (False, None) - sage: h = f/3 - sage: h.is_square() - True - sage: h.is_square(root=True) - (True, 2*x^2 + 8*x + 6) - - .. NOTE:: - - This is the is_square implementation for general commutative ring - elements. It's implementation is to raise a - :exc:`NotImplementedError`. The function definition is here to show - what functionality is expected and provide a general framework. - """ - raise NotImplementedError("is_square() not implemented for elements of %s" % self.parent()) - - def sqrt(self, extend=True, all=False, name=None): - """ - Compute the square root. - - INPUT: - - - ``extend`` -- boolean (default: ``True``); whether to make a ring - extension containing a square root if ``self`` is not a square - - - ``all`` -- boolean (default: ``False``); whether to return a list of - all square roots or just a square root - - - ``name`` -- required when ``extend=True`` and ``self`` is not a - square; this will be the name of the generator of the extension - - OUTPUT: - - - if ``all=False``, a square root; raises an error if ``extend=False`` - and ``self`` is not a square - - - if ``all=True``, a list of all the square roots (empty if - ``extend=False`` and ``self`` is not a square) - - ALGORITHM: - - It uses ``is_square(root=true)`` for the hard part of the work, the rest - is just wrapper code. - - EXAMPLES:: - - sage: # needs sage.libs.pari - sage: R. = ZZ[] - sage: (x^2).sqrt() - x - sage: f = x^2 - 4*x + 4; f.sqrt(all=True) - [x - 2, -x + 2] - sage: sqrtx = x.sqrt(name='y'); sqrtx - y - sage: sqrtx^2 - x - sage: x.sqrt(all=true, name='y') - [y, -y] - sage: x.sqrt(extend=False, all=True) - [] - sage: x.sqrt() - Traceback (most recent call last): - ... - TypeError: Polynomial is not a square. You must specify the name - of the square root when using the default extend = True - sage: x.sqrt(extend=False) - Traceback (most recent call last): - ... - ValueError: trying to take square root of non-square x with extend = False - - TESTS:: - - sage: # needs sage.libs.pari - sage: f = (x + 3)^2; f.sqrt() - x + 3 - sage: f = (x + 3)^2; f.sqrt(all=True) - [x + 3, -x - 3] - sage: f = (x^2 - x + 3)^2; f.sqrt() - x^2 - x + 3 - sage: f = (x^2 - x + 3)^6; f.sqrt() - x^6 - 3*x^5 + 12*x^4 - 19*x^3 + 36*x^2 - 27*x + 27 - sage: g = (R.random_element(15))^2 - sage: g.sqrt()^2 == g - True - - sage: # needs sage.libs.pari - sage: R. = GF(250037)[] - sage: f = x^2/(x+1)^2; f.sqrt() - x/(x + 1) - sage: f = 9 * x^4 / (x+1)^2; f.sqrt() - 3*x^2/(x + 1) - sage: f = 9 * x^4 / (x+1)^2; f.sqrt(all=True) - [3*x^2/(x + 1), 250034*x^2/(x + 1)] - - sage: R. = QQ[] - sage: a = 2*(x+1)^2 / (2*(x-1)^2); a.sqrt() - (x + 1)/(x - 1) - sage: sqrtx=(1/x).sqrt(name='y'); sqrtx - y - sage: sqrtx^2 - 1/x - sage: (1/x).sqrt(all=true, name='y') - [y, -y] - sage: (1/x).sqrt(extend=False, all=True) - [] - sage: (1/(x^2-1)).sqrt() - Traceback (most recent call last): - ... - TypeError: Polynomial is not a square. You must specify the name - of the square root when using the default extend = True - sage: (1/(x^2-3)).sqrt(extend=False) - Traceback (most recent call last): - ... - ValueError: trying to take square root of non-square 1/(x^2 - 3) with extend = False - """ - # This code is very general, it works for all integral domains that have the - # is_square(root = True) option - - from sage.categories.integral_domains import IntegralDomains - P = self._parent - is_sqr, sq_rt = self.is_square(root=True) - if is_sqr: - if all: - if P not in IntegralDomains(): - raise NotImplementedError('sqrt() with all=True is only implemented for integral domains, not for %s' % P) - if P.characteristic()==2 or sq_rt==0: - # 0 has only one square root, and in characteristic 2 everything also has only 1 root - return [sq_rt] - return [sq_rt, -sq_rt] - return sq_rt - # from now on we know that self is not a square - if P not in IntegralDomains(): - raise NotImplementedError('sqrt() of non squares is only implemented for integral domains, not for %s' % P) - if not extend: - # all square roots of a non-square should be an empty list - if all: - return [] - raise ValueError('trying to take square root of non-square %s with extend = False' % self) - - if name is None: - raise TypeError("Polynomial is not a square. You must specify the name of the square root when using the default extend = True") - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - PY = PolynomialRing(P, 'y') - y = PY.gen() - sq_rt = PY.quotient(y**2-self, names = name)(y) - if all: - if P.characteristic() == 2: - return [sq_rt] - return [sq_rt, -sq_rt] - return sq_rt ##############################################