Skip to content

Commit 2732ffd

Browse files
author
Release Manager
committed
gh-40494: Speed up `roots` when `multiplicities=False` for polynomials over finite fields Speed up `f.roots` when `multiplicities=False` for univariate polynomials over finite fields - as seen in this blog post: https://adib.au/2025/lance-hard/#speedup- by-using-gcd - also mentioned here #35556 ```sage sage: p = random_prime(2^200) sage: F = GF(p) sage: R.<x> = F[] sage: f = R.random_element(degree=20000) sage: %time f.roots(multiplicities=False) # would hang / be very slow before this change CPU times: user 5.24 s, sys: 16.4 ms, total: 5.26 s Wall time: 5.26 s [205828247017582431219769663595709973936079117296023914605767] # just some roots, will ofc be different every run :) ``` ### 📝 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. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. URL: #40494 Reported by: Arthur Reviewer(s): Arthur, user202729
2 parents 01be49c + f033e82 commit 2732ffd

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed

src/sage/rings/finite_rings/finite_field_base.pyx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,6 +2120,83 @@ cdef class FiniteField(Field):
21202120
python_int = int.from_bytes(input_bytes, byteorder=byteorder)
21212121
return self.from_integer(python_int)
21222122

2123+
def _roots_univariate_polynomial(self, f, ring, multiplicities, algorithm=None):
2124+
r"""
2125+
Return the roots of the univariate polynomial ``f``.
2126+
2127+
INPUT:
2128+
2129+
- ``f`` -- a polynomial defined over this field
2130+
2131+
- ``ring`` -- the ring to find roots in.
2132+
2133+
- ``multiplicities`` -- bool (default: ``True``). If ``True``, return
2134+
list of pairs `(r, n)`, where `r` is a root and `n` is its
2135+
multiplicity. If ``False``, just return the unique roots, with no
2136+
information about multiplicities.
2137+
2138+
- ``algorithm`` -- ignored
2139+
2140+
TESTS:
2141+
2142+
We can take the roots of a polynomial defined over a finite field::
2143+
2144+
sage: set_random_seed(31337)
2145+
sage: p = random_prime(2^128)
2146+
sage: R.<x> = Zmod(p)[]
2147+
sage: f = R.random_element(degree=15)
2148+
sage: f.roots()
2149+
[(117558869610275297997958296126212805270, 1)]
2150+
2151+
We can take the roots of a polynomial defined over a finite field without multiplicities::
2152+
2153+
sage: set_random_seed(31337)
2154+
sage: p = random_prime(2^128)
2155+
sage: R.<x> = Zmod(p)[]
2156+
sage: f = R.random_element(degree=150)
2157+
sage: f.roots(multiplicities=False)
2158+
[116560079209701720510648792531840294827]
2159+
2160+
We can take the roots of a polynomial defined over a finite field extension::
2161+
2162+
sage: set_random_seed(31337)
2163+
sage: F.<a> = GF((2, 10))
2164+
sage: R.<x> = F[]
2165+
sage: f = R.random_element(degree=10)
2166+
sage: f.roots()
2167+
[(a^9 + a^8 + a^6 + a^4 + a^2, 1)]
2168+
sage: f.roots(multiplicities=False)
2169+
[a^9 + a^8 + a^6 + a^4 + a^2]
2170+
2171+
We can take the roots of a high degree polynomial in a reasonable time::
2172+
2173+
sage: set_random_seed(31337)
2174+
sage: p = random_prime(2^128)
2175+
sage: F = GF(p)
2176+
sage: R.<x> = F[]
2177+
sage: f = R.random_element(degree=10000)
2178+
sage: f.roots(multiplicities=False)
2179+
[65940671326230628578511607550463701471]
2180+
"""
2181+
if algorithm is not None:
2182+
raise NotImplementedError
2183+
2184+
K = f.base_ring()
2185+
L = K if ring is None else ring
2186+
2187+
if K != L:
2188+
raise NotImplementedError
2189+
2190+
if multiplicities:
2191+
return f._roots_from_factorization(f.factor(), multiplicities)
2192+
else:
2193+
R = f.parent()
2194+
x = R.gen()
2195+
p = K.order()
2196+
g = pow(x, p, f) - x
2197+
g = f.gcd(g)
2198+
return g._roots_from_factorization(g.factor(), False)
2199+
21232200

21242201
def unpickle_FiniteField_ext(_type, order, variable_name, modulus, kwargs):
21252202
r"""

src/sage/rings/finite_rings/integer_mod_ring.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,7 +1774,7 @@ def _roots_univariate_polynomial(self, f, ring=None, multiplicities=True, algori
17741774
[0, 32, 9]
17751775
sage: (x^6 + x^5 + 9*x^4 + 20*x^3 + 3*x^2 + 18*x + 7).roots()
17761776
[(19, 1), (20, 2), (21, 3)]
1777-
sage: (x^6 + x^5 + 9*x^4 + 20*x^3 + 3*x^2 + 18*x + 7).roots(multiplicities=False)
1777+
sage: sorted((x^6 + x^5 + 9*x^4 + 20*x^3 + 3*x^2 + 18*x + 7).roots(multiplicities=False))
17781778
[19, 20, 21]
17791779
17801780
We can find roots without multiplicities over a ring whose modulus is
@@ -1911,6 +1911,15 @@ def _roots_univariate_polynomial(self, f, ring=None, multiplicities=True, algori
19111911
sage: R.<x> = Zmod(100)[]
19121912
sage: (x^2 - 1).roots(Zmod(99), multiplicities=False) == (x^2 - 1).change_ring(Zmod(99)).roots(multiplicities=False)
19131913
True
1914+
1915+
We can find roots of high degree polynomials in a reasonable time:
1916+
1917+
sage: set_random_seed(31337)
1918+
sage: p = random_prime(2^128)
1919+
sage: R.<x> = Zmod(p)[]
1920+
sage: f = R.random_element(degree=5000)
1921+
sage: f.roots(multiplicities=False)
1922+
[107295314027801680550847462044796892009, 75545907600948005385964943744536832524]
19141923
"""
19151924

19161925
# This function only supports roots in an IntegerModRing
@@ -1926,7 +1935,7 @@ def _roots_univariate_polynomial(self, f, ring=None, multiplicities=True, algori
19261935
" implemented (try the multiplicities=False option)"
19271936
)
19281937
# Roots of non-zero polynomial over finite fields by factorization
1929-
return f._roots_from_factorization(f.factor(), multiplicities)
1938+
return f.change_ring(f.base_ring().field()).roots(multiplicities=multiplicities)
19301939

19311940
# Zero polynomial is a base case
19321941
if deg < 0:
@@ -1935,7 +1944,12 @@ def _roots_univariate_polynomial(self, f, ring=None, multiplicities=True, algori
19351944

19361945
# Finite fields are a base case
19371946
if self.is_field():
1938-
return f._roots_from_factorization(f.factor(), False)
1947+
return list(
1948+
map(
1949+
f.base_ring(),
1950+
f.change_ring(f.base_ring().field()).roots(multiplicities=False),
1951+
)
1952+
)
19391953

19401954
# Otherwise, find roots modulo each prime power
19411955
fac = self.factored_order()

src/sage/rings/polynomial/polynomial_element.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8377,8 +8377,8 @@ cdef class Polynomial(CommutativePolynomial):
83778377
sage: A = PolynomialRing(R, 'y')
83788378
sage: y = A.gen()
83798379
sage: f = 10*y^2 - y^3 - 9
8380-
sage: f.roots(multiplicities=False) # needs sage.libs.pari
8381-
[1, 0, 3, 6]
8380+
sage: sorted(f.roots(multiplicities=False)) # needs sage.libs.pari
8381+
[0, 1, 3, 6]
83828382
83838383
An example over the complex double field (where root finding is
83848384
fast, thanks to NumPy)::

0 commit comments

Comments
 (0)