Skip to content

Commit 5741ca3

Browse files
author
Release Manager
committed
sagemathgh-40401: Implement Square Roots to FiniteFields Category <!-- ^ Please provide a concise and informative title. --> <!-- ^ Don't put issue numbers in the title, do this in the PR description below. --> <!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method to calculate 1 + 2". --> <!-- v Describe your changes below in detail. --> <!-- v Why is this change required? What problem does it solve? --> <!-- v If this PR resolves an open issue, please link to it here. For example, "Fixes sagemath#12345". --> Fixes sagemath#39688 Fixes sagemath#39798 Added the following methods to the finite field category - `quadratic_non_residue()` - `square_root()` and `sqrt()` - `_tonelli()` and `_cipolla()`: the algorithms for finding a square root - `is_square()` Moved `is_square()` and `sqrt()` methods from element.pyx to their proper categories. Most profiling tests showed that Tonelli's algorithm outperforms Cipolla. However a profile with a Finite Field with order of a Cullen prime resulted in Cipolla outperforming Tonelli. I'm running the meson build and currently cannot find the way to build the html documentation. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> URL: sagemath#40401 Reported by: Brian Heckel Reviewer(s): Brian Heckel, Frédéric Chapoton, Vincent Macri
2 parents 8dbc8e7 + 51c1aa6 commit 5741ca3

File tree

4 files changed

+421
-184
lines changed

4 files changed

+421
-184
lines changed

src/sage/categories/commutative_rings.py

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,181 @@ def _pseudo_fraction_field(self):
576576
return coercion_model.division_parent(self)
577577

578578
class ElementMethods:
579-
pass
579+
def is_square(self, root=False):
580+
"""
581+
Return whether or not the ring element ``self`` is a square.
582+
If the optional argument root is ``True``, then also return
583+
the square root (or ``None``, if it is not a square).
584+
585+
INPUT:
586+
587+
- ``root`` -- boolean (default: ``False``); whether or not to also
588+
return a square root
589+
590+
OUTPUT:
591+
592+
- boolean; whether or not a square
593+
594+
- object; (optional) an actual square root if found, and ``None``
595+
otherwise
596+
597+
EXAMPLES::
598+
599+
sage: R.<x> = PolynomialRing(QQ)
600+
sage: f = 12*(x+1)^2 * (x+3)^2
601+
sage: f.is_square()
602+
False
603+
sage: f.is_square(root=True)
604+
(False, None)
605+
sage: h = f/3
606+
sage: h.is_square()
607+
True
608+
sage: h.is_square(root=True)
609+
(True, 2*x^2 + 8*x + 6)
610+
611+
.. NOTE::
612+
613+
This is the is_square implementation for general commutative ring
614+
elements. It's implementation is to raise a
615+
:exc:`NotImplementedError`. The function definition is here to show
616+
what functionality is expected and provide a general framework.
617+
"""
618+
raise NotImplementedError("is_square() not implemented for elements of %s" % self.parent())
619+
620+
def sqrt(self, extend=True, all=False, name=None):
621+
"""
622+
Compute the square root.
623+
624+
INPUT:
625+
626+
- ``extend`` -- boolean (default: ``True``); whether to make a ring
627+
extension containing a square root if ``self`` is not a square
628+
629+
- ``all`` -- boolean (default: ``False``); whether to return a list of
630+
all square roots or just a square root
631+
632+
- ``name`` -- required when ``extend=True`` and ``self`` is not a
633+
square; this will be the name of the generator of the extension
634+
635+
OUTPUT:
636+
637+
- if ``all=False``, a square root; raises an error if ``extend=False``
638+
and ``self`` is not a square
639+
640+
- if ``all=True``, a list of all the square roots (empty if
641+
``extend=False`` and ``self`` is not a square)
642+
643+
ALGORITHM:
644+
645+
It uses ``is_square(root=true)`` for the hard part of the work, the rest
646+
is just wrapper code.
647+
648+
EXAMPLES::
649+
650+
sage: # needs sage.libs.pari
651+
sage: R.<x> = ZZ[]
652+
sage: (x^2).sqrt()
653+
x
654+
sage: f = x^2 - 4*x + 4; f.sqrt(all=True)
655+
[x - 2, -x + 2]
656+
sage: sqrtx = x.sqrt(name='y'); sqrtx
657+
y
658+
sage: sqrtx^2
659+
x
660+
sage: x.sqrt(all=true, name='y')
661+
[y, -y]
662+
sage: x.sqrt(extend=False, all=True)
663+
[]
664+
sage: x.sqrt()
665+
Traceback (most recent call last):
666+
...
667+
TypeError: Polynomial is not a square. You must specify the name
668+
of the square root when using the default extend = True
669+
sage: x.sqrt(extend=False)
670+
Traceback (most recent call last):
671+
...
672+
ValueError: trying to take square root of non-square x with extend = False
673+
674+
TESTS::
675+
676+
sage: # needs sage.libs.pari
677+
sage: f = (x + 3)^2; f.sqrt()
678+
x + 3
679+
sage: f = (x + 3)^2; f.sqrt(all=True)
680+
[x + 3, -x - 3]
681+
sage: f = (x^2 - x + 3)^2; f.sqrt()
682+
x^2 - x + 3
683+
sage: f = (x^2 - x + 3)^6; f.sqrt()
684+
x^6 - 3*x^5 + 12*x^4 - 19*x^3 + 36*x^2 - 27*x + 27
685+
sage: g = (R.random_element(15))^2
686+
sage: g.sqrt()^2 == g
687+
True
688+
689+
sage: # needs sage.libs.pari
690+
sage: R.<x> = GF(250037)[]
691+
sage: f = x^2/(x+1)^2; f.sqrt()
692+
x/(x + 1)
693+
sage: f = 9 * x^4 / (x+1)^2; f.sqrt()
694+
3*x^2/(x + 1)
695+
sage: f = 9 * x^4 / (x+1)^2; f.sqrt(all=True)
696+
[3*x^2/(x + 1), 250034*x^2/(x + 1)]
697+
698+
sage: R.<x> = QQ[]
699+
sage: a = 2*(x+1)^2 / (2*(x-1)^2); a.sqrt()
700+
(x + 1)/(x - 1)
701+
sage: sqrtx = (1/x).sqrt(name='y'); sqrtx
702+
y
703+
sage: sqrtx^2
704+
1/x
705+
sage: (1/x).sqrt(all=true, name='y')
706+
[y, -y]
707+
sage: (1/x).sqrt(extend=False, all=True)
708+
[]
709+
sage: (1/(x^2-1)).sqrt()
710+
Traceback (most recent call last):
711+
...
712+
TypeError: Polynomial is not a square. You must specify the name
713+
of the square root when using the default extend = True
714+
sage: (1/(x^2-3)).sqrt(extend=False)
715+
Traceback (most recent call last):
716+
...
717+
ValueError: trying to take square root of non-square 1/(x^2 - 3) with extend = False
718+
"""
719+
# This code is very general, it works for all integral domains that have the
720+
# is_square(root = True) option
721+
722+
from sage.categories.integral_domains import IntegralDomains
723+
P = self.parent()
724+
is_sqr, sq_rt = self.is_square(root=True)
725+
if is_sqr:
726+
if all:
727+
if P not in IntegralDomains():
728+
raise NotImplementedError('sqrt() with all=True is only implemented for integral domains, not for %s' % P)
729+
if P.characteristic() == 2 or sq_rt == 0:
730+
# 0 has only one square root, and in characteristic 2 everything also has only 1 root
731+
return [sq_rt]
732+
return [sq_rt, -sq_rt]
733+
return sq_rt
734+
# from now on we know that self is not a square
735+
if P not in IntegralDomains():
736+
raise NotImplementedError('sqrt() of non squares is only implemented for integral domains, not for %s' % P)
737+
if not extend:
738+
# all square roots of a non-square should be an empty list
739+
if all:
740+
return []
741+
raise ValueError('trying to take square root of non-square %s with extend = False' % self)
742+
743+
if name is None:
744+
raise TypeError("Polynomial is not a square. You must specify the name of the square root when using the default extend = True")
745+
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
746+
PY = PolynomialRing(P, 'y')
747+
y = PY.gen()
748+
sq_rt = PY.quotient(y**2-self, names=name)(y)
749+
if all:
750+
if P.characteristic() == 2:
751+
return [sq_rt]
752+
return [sq_rt, -sq_rt]
753+
return sq_rt
580754

581755
class Finite(CategoryWithAxiom):
582756
r"""

0 commit comments

Comments
 (0)