@@ -134,7 +134,6 @@ from sage.categories.morphism cimport Morphism
134
134
from sage.misc.superseded import deprecation_cython as deprecation, deprecated_function_alias
135
135
from sage.misc.cachefunc import cached_method
136
136
137
-
138
137
cpdef is_Polynomial(f) noexcept:
139
138
"""
140
139
Return ``True`` if ``f`` is of type univariate polynomial.
@@ -2151,6 +2150,18 @@ cdef class Polynomial(CommutativePolynomial):
2151
2150
True
2152
2151
sage: f % f actor == 0
2153
2152
True
2153
+
2154
+ TESTS:
2155
+
2156
+ Ensure that :issue:`37445` is fixed::
2157
+
2158
+ sage: R.<x> = GF(13)[]
2159
+ sage: def irr(d, R): return f.monic() if (f := R.random_element(d)).is_irreducible() else irr(d, R)
2160
+ sage: f = prod(irr(6, R) for _ in range(10))
2161
+ sage: irr = f._cantor_zassenhaus_split_to_irreducible(6)
2162
+ sage: assert irr.degree() == 6
2163
+ sage: assert f % i rr == 0
2164
+ sage: assert irr.is_irreducible()
2154
2165
"""
2155
2166
R = self .parent()
2156
2167
q = self .base_ring().order()
@@ -2160,8 +2171,8 @@ cdef class Polynomial(CommutativePolynomial):
2160
2171
return self
2161
2172
2162
2173
# We expect to succeed with greater than 1/2 probability,
2163
- # so if we try 1000 times and fail, there's a bug somewhere.
2164
- for _ in range (1000 ):
2174
+ # so if we try 100 times and fail, there's a bug somewhere.
2175
+ for _ in range (100 ):
2165
2176
# Sample a polynomial "uniformly" from R
2166
2177
# TODO: once #37118 has been merged, this can be made cleaner,
2167
2178
# as we will actually have access to uniform sampling.
@@ -2174,7 +2185,7 @@ cdef class Polynomial(CommutativePolynomial):
2174
2185
2175
2186
# Need to handle odd and even characteristic separately
2176
2187
if q % 2 :
2177
- h = self .gcd(pow (T, (q- 1 )// 2 , self ) - 1 )
2188
+ h = self .gcd(pow (T, (q** degree - 1 )// 2 , self ) - 1 )
2178
2189
else :
2179
2190
# Compute the trace of T with field of order 2^k
2180
2191
# sum T^(2^i) for i in range (degree * k)
@@ -2200,20 +2211,23 @@ cdef class Polynomial(CommutativePolynomial):
2200
2211
# If you are reaching this error, chances are there's a bug in the code.
2201
2212
raise AssertionError (f" no splitting of degree {degree} found for {self}" )
2202
2213
2203
- def _any_irreducible_factor_squarefree (self , degree = None ):
2214
+ def _any_irreducible_factor_squarefree (self , degree = None , ext_degree = None ):
2204
2215
"""
2205
- Helper function for any_irreducible_factor which computes
2216
+ Helper function for :meth:` any_irreducible_factor` which computes
2206
2217
an irreducible factor from self, assuming the input is
2207
2218
squarefree.
2208
2219
2209
2220
Does this by first computing the distinct degree factorisations
2210
2221
of self and then finds a factor with Cantor-Zassenhaus
2211
2222
splitting.
2212
2223
2213
- If degree is not None, then only irreducible factors of degree
2214
- `degree` are searched for, otherwise the smallest degree factor
2224
+ If `` degree`` is not `` None`` , then only irreducible factors of degree
2225
+ `` degree` ` are searched for, otherwise the smallest degree factor
2215
2226
is found.
2216
2227
2228
+ If ``ext_degree`` is not ``None``, then only irreducible factors whose
2229
+ degree divides ``ext_degree`` are returned.
2230
+
2217
2231
EXAMPLES::
2218
2232
2219
2233
sage: # needs sage.rings.finite_rings
@@ -2262,10 +2276,15 @@ cdef class Polynomial(CommutativePolynomial):
2262
2276
2263
2277
# Otherwise we use the smallest possible d value
2264
2278
for (poly, d) in self ._distinct_degree_factorisation_squarefree():
2265
- return poly._cantor_zassenhaus_split_to_irreducible(d)
2266
- raise ValueError (f" no irreducible factor could be computed from {self}" )
2279
+ if ext_degree is None :
2280
+ return poly._cantor_zassenhaus_split_to_irreducible(d)
2281
+ elif ZZ(d).divides(ext_degree):
2282
+ return poly._cantor_zassenhaus_split_to_irreducible(d)
2283
+ if d > ext_degree:
2284
+ raise ValueError (f" no irreducible factor of degree {degree} dividing {ext_degree} could be computed from {self}" )
2285
+ raise AssertionError (f" no irreducible factor could be computed from {self}" )
2267
2286
2268
- def any_irreducible_factor (self , degree = None , assume_squarefree = False , assume_distinct_deg = False ):
2287
+ def any_irreducible_factor (self , degree = None , assume_squarefree = False , assume_equal_deg = False , ext_degree = None ):
2269
2288
"""
2270
2289
Return an irreducible factor of this polynomial.
2271
2290
@@ -2281,11 +2300,15 @@ cdef class Polynomial(CommutativePolynomial):
2281
2300
Used for polynomials over finite fields. If ``True``,
2282
2301
this polynomial is assumed to be squarefree.
2283
2302
2284
- - ``assume_distinct_deg `` (boolean) -- (default: ``False``).
2303
+ - ``assume_equal_deg `` (boolean) -- (default: ``False``).
2285
2304
Used for polynomials over finite fields. If ``True``,
2286
2305
this polynomial is assumed to be the product of irreducible
2287
2306
polynomials of degree equal to ``degree``.
2288
2307
2308
+ - ``ext_degree`` -- positive integer or ``None`` (default);
2309
+ used for polynomials over finite fields. If not ``None`` only returns
2310
+ irreducible factors of ``self`` whose degree divides ``ext_degree``.
2311
+
2289
2312
EXAMPLES::
2290
2313
2291
2314
sage: # needs sage.rings.finite_rings
@@ -2328,9 +2351,9 @@ cdef class Polynomial(CommutativePolynomial):
2328
2351
sage: F = GF(163)
2329
2352
sage: R.<x> = F[]
2330
2353
sage: h = (x + 57) * (x + 98) * (x + 117) * (x + 145)
2331
- sage: h.any_irreducible_factor(degree=1, assume_distinct_deg =True) # random
2354
+ sage: h.any_irreducible_factor(degree=1, assume_equal_deg =True) # random
2332
2355
x + 98
2333
- sage: h.any_irreducible_factor(assume_distinct_deg =True)
2356
+ sage: h.any_irreducible_factor(assume_equal_deg =True)
2334
2357
Traceback (most recent call last):
2335
2358
...
2336
2359
ValueError: degree must be known if distinct degree factorisation is assumed
@@ -2359,7 +2382,7 @@ cdef class Polynomial(CommutativePolynomial):
2359
2382
if degree < 1 :
2360
2383
raise ValueError (f" {degree = } must be positive" )
2361
2384
2362
- if assume_distinct_deg and degree is None :
2385
+ if assume_equal_deg and degree is None :
2363
2386
raise ValueError (" degree must be known if distinct degree factorisation is assumed" )
2364
2387
2365
2388
# When not working over a finite field, do the simple thing of factoring.
@@ -2397,27 +2420,30 @@ cdef class Polynomial(CommutativePolynomial):
2397
2420
2398
2421
# If we know the polynomial is square-free, we can start here
2399
2422
if assume_squarefree:
2400
- if assume_distinct_deg :
2423
+ if assume_equal_deg :
2401
2424
return self ._cantor_zassenhaus_split_to_irreducible(degree)
2402
- return self ._any_irreducible_factor_squarefree(degree)
2425
+ return self ._any_irreducible_factor_squarefree(degree, ext_degree )
2403
2426
2404
2427
# Otherwise we compute the squarefree decomposition and check each
2405
2428
# polynomial for a root. If no poly has a root, we raise an error.
2406
2429
SFD = self .squarefree_decomposition()
2407
2430
SFD.sort()
2408
2431
for poly, _ in SFD:
2409
2432
try :
2410
- return poly._any_irreducible_factor_squarefree(degree)
2433
+ return poly._any_irreducible_factor_squarefree(degree, ext_degree )
2411
2434
except ValueError :
2412
2435
pass
2413
2436
2414
2437
# If degree has been set, there could just be no factor of the desired degree
2415
2438
if degree:
2416
2439
raise ValueError (f" polynomial {self} has no irreducible factor of degree {degree}" )
2440
+ # If ext_degree has been set, then there may be no irreducible factor of degree dividing ext_degree
2441
+ if ext_degree:
2442
+ raise ValueError (f" polynomial {self} has no irreducible factor of degree dividing {ext_degree}" )
2417
2443
# But if any degree is allowed then there should certainly be a factor if self has degree > 0
2418
2444
raise AssertionError (f" no irreducible factor was computed for {self}. Bug." )
2419
2445
2420
- def any_root (self , ring = None , degree = None , assume_squarefree = False , assume_distinct_deg = False ):
2446
+ def any_root (self , ring = None , degree = None , assume_squarefree = False , assume_equal_deg = False ):
2421
2447
"""
2422
2448
Return a root of this polynomial in the given ring.
2423
2449
@@ -2437,14 +2463,26 @@ cdef class Polynomial(CommutativePolynomial):
2437
2463
finite fields. If ``True``, this polynomial is assumed to be
2438
2464
squarefree.
2439
2465
2440
- - ``assume_distinct_deg `` (bool) -- Used for polynomials over
2466
+ - ``assume_equal_deg `` (bool) -- Used for polynomials over
2441
2467
finite fields. If ``True``, all factors of this polynomial
2442
- are assumed to have degree ``degree``.
2468
+ are assumed to have degree ``degree``. Note that ``degree``
2469
+ must be set.
2443
2470
2444
2471
.. WARNING::
2445
2472
2446
2473
Negative degree input will be deprecated. Instead use
2447
- ``assume_distinct_deg``.
2474
+ ``assume_equal_deg``.
2475
+
2476
+ .. NOTE::
2477
+
2478
+ For finite fields, ``any_root()`` is non-deterministic when
2479
+ finding linear roots of a polynomial over the base ring.
2480
+ However, if ``degree`` is greater than one, or ``ring`` is an
2481
+ extension of the base ring, then the root computed is found
2482
+ by attempting to return a root after factorisation. Roots found
2483
+ in this way are deterministic. This may change in the future.
2484
+ For all other rings or fields, roots are found by first
2485
+ fully-factoring ``self`` and the output is deterministic.
2448
2486
2449
2487
EXAMPLES::
2450
2488
@@ -2554,38 +2592,76 @@ cdef class Polynomial(CommutativePolynomial):
2554
2592
# When not working over a finite field, do the simple thing of factoring for
2555
2593
# roots and picking the first root. If none are available, raise an error.
2556
2594
from sage.categories.finite_fields import FiniteFields
2557
- if not self .base_ring() in FiniteFields():
2558
- rs = self .roots(ring = ring, multiplicities = False )
2559
- if rs:
2560
- return rs[0 ]
2561
- raise ValueError (f" polynomial {self} has no roots" )
2595
+ if self .base_ring() not in FiniteFields():
2596
+ if ring not in FiniteFields():
2597
+ rs = self .roots(ring = ring, multiplicities = False )
2598
+ if rs:
2599
+ return rs[0 ]
2600
+ raise ValueError (f" polynomial {self} has no roots" )
2601
+
2602
+ # Ensure that a provided ring is appropriate for the function. From the
2603
+ # above we know it is either None or a finite field. When it's a finite
2604
+ # field we ensure there's a coercion from the base ring to ring.
2605
+ if ring is not None :
2606
+ if ring.coerce_map_from(self .base_ring()) is None :
2607
+ raise ValueError (f" no coercion map can be computed from {self.base_ring()} to {ring}" )
2562
2608
2563
2609
# When the degree is none, we only look for a linear factor
2564
2610
if degree is None :
2565
- # if a ring is given try and coerce the polynomial into this ring
2566
- if ring is not None :
2611
+ # When ring is None, we attempt to find a linear factor of self
2612
+ if ring is None :
2567
2613
try :
2568
- self = self .change_ring(ring )
2614
+ f = self .any_irreducible_factor( degree = 1 , assume_squarefree = assume_squarefree )
2569
2615
except ValueError :
2570
- raise (f" cannot coerce polynomial {self} to the new ring: {ring}" )
2616
+ raise ValueError (f" no root of polynomial {self} can be computed" )
2617
+ return - f[0 ] / f[1 ]
2571
2618
2572
- # try and find a linear irreducible polynomial from f to compute a root
2619
+ # When we have a ring, then we can find an irreducible factor of degree `d` providing
2620
+ # that d divides the degree of the extension from the base ring to the given ring
2621
+ allowed_extension_degree = ring.degree() // self .base_ring().degree()
2573
2622
try :
2574
- f = self .any_irreducible_factor(degree = 1 , assume_squarefree = assume_squarefree )
2623
+ f = self .any_irreducible_factor(assume_squarefree = assume_squarefree, ext_degree = allowed_extension_degree )
2575
2624
except ValueError :
2576
- raise ValueError (f" no root of polynomial {self} can be computed" )
2577
-
2578
- return - f[0 ] / f[1 ]
2625
+ raise ValueError (f" no root of polynomial {self} can be computed over the ring {ring}" )
2626
+ # When d != 1 we then find the smallest extension
2627
+ # TODO: What we should do here is compute some minimal
2628
+ # extension F_ext = self.base_ring().extension(d, names="a") and find a
2629
+ # root here and then coerce this root into the parent ring. This means we
2630
+ # would work with the smallest possible extension.
2631
+ # However, if we have some element of GF(p^k) and we try and coerce this to
2632
+ # some element GF(p^(k*n)) this can fail, even though mathematically it
2633
+ # should be fine.
2634
+ # TODO: Additionally, if the above was solved, it would be faster to extend the base
2635
+ # ring with the irreducible factor however, if the base ring is an extension
2636
+ # then the type of self.base_ring().extension(f) is a Univariate Quotient Polynomial Ring
2637
+ # and not a finite field.
2638
+
2639
+ # When f has degree one we simply return the roots
2640
+ # TODO: should we write something fast for degree two using
2641
+ # the quadratic formula?
2642
+ if f.degree().is_one():
2643
+ root = - f[0 ] / f[1 ]
2644
+ return ring(root)
2645
+
2646
+ # TODO: The proper thing to do here would be to call
2647
+ # return f.change_ring(ring).any_root()
2648
+ # but as we cannot work in the minimal extension (see above) working
2649
+ # in the extension for f.change_ring(ring).any_root() is almost always
2650
+ # much much much slower than using the call for roots() which uses
2651
+ # C library bindings for all finite fields.
2652
+ # Until the coercion system for finite fields works better,
2653
+ # this will be the most performant
2654
+ return f.roots(ring, multiplicities = False )[0 ]
2579
2655
2580
2656
# The old version of `any_root()` allowed degree < 0 to indicate that the input polynomial
2581
2657
# had a distinct degree factorisation, we pass this to any_irreducible_factor as a bool and
2582
2658
# ensure that the degree is positive.
2583
2659
degree = ZZ(degree)
2584
2660
if degree < 0 :
2585
2661
from sage.misc.superseded import deprecation
2586
- deprecation(37170 , " negative ``degree`` will be disallowed. Instead use the bool `assume_distinct_deg `." )
2662
+ deprecation(37170 , " negative ``degree`` will be disallowed. Instead use the bool `assume_equal_deg `." )
2587
2663
degree = - degree
2588
- assume_distinct_deg = True
2664
+ assume_equal_deg = True
2589
2665
2590
2666
# If a certain degree is requested, then we find an irreducible factor of degree `degree`
2591
2667
# use this to compute a field extension and return the generator as root of this polynomial
@@ -2594,7 +2670,7 @@ cdef class Polynomial(CommutativePolynomial):
2594
2670
try :
2595
2671
f = self .any_irreducible_factor(degree = degree,
2596
2672
assume_squarefree = assume_squarefree,
2597
- assume_distinct_deg = assume_distinct_deg )
2673
+ assume_equal_deg = assume_equal_deg )
2598
2674
except ValueError :
2599
2675
raise ValueError (f" no irreducible factor of degree {degree} can be computed from {self}" )
2600
2676
@@ -2617,13 +2693,17 @@ cdef class Polynomial(CommutativePolynomial):
2617
2693
# FiniteField type if the base field is a non-prime field,
2618
2694
# so this slower option is chosen to ensure the root is
2619
2695
# over explicitly a FiniteField type.
2620
- ring = self .base_ring().extension(f.degree(), names = " a" )
2621
-
2622
- # Now we look for a linear root of this irreducible polynomial of degree `degree`
2623
- # over the user supplied ring or the extension we just computed. If the user sent
2624
- # a bad ring here of course there may be no root found.
2625
- f = f.change_ring(ring)
2626
- return f.any_root()
2696
+ ring = self .base_ring().extension(degree, names = " a" )
2697
+
2698
+ # TODO: The proper thing to do here would be to call
2699
+ # return f.change_ring(ring).any_root()
2700
+ # but as we cannot work in the minimal extension (see above) working
2701
+ # in the extension for f.change_ring(ring).any_root() is almost always
2702
+ # much much much slower than using the call for roots() which uses
2703
+ # C library bindings for all finite fields.
2704
+ # Until the coercion system for finite fields works better,
2705
+ # this will be the most performant
2706
+ return f.roots(ring, multiplicities = False )[0 ]
2627
2707
2628
2708
def __truediv__ (left , right ):
2629
2709
r """
0 commit comments