Skip to content

Commit 48ef6df

Browse files
author
Release Manager
committed
gh-36097: Add additional bindings from NTL to `Polynomial_ZZ_pEX` There are current cython bindings to NTL methods which are not made available to polynomial rings over extension fields. For some of these bindings, their introduction does not offer much speed up, but there is a 50x improvement for using NTL reverse over that of the current implementation, and about a 20% speed up using the NTL `ZZ_pEX_InvTrunc` over what is currently implemented. Method names and functionalities have been preserved, so this simply improves performance for the `Polynomial_ZZ_pEX` type without effecting other polynomial ring classes. #### Before Patch ```py sage: p = next_prime(2**1024) sage: F.<i> = GF(p^2) sage: R.<x> = PolynomialRing(F, implementation="NTL") sage: sage: f = R([p for p in primes(10)]) sage: %timeit f.reverse() 125 µs ± 582 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each) sage: %timeit f.inverse_series_trunc(500) 4.96 ms ± 81.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) sage: sage: f = R([p for p in primes(100)]) sage: %timeit f.reverse() 681 µs ± 4.21 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) sage: %timeit f.inverse_series_trunc(500) 7.1 ms ± 62.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) sage: sage: f = R([p for p in primes(1000)]) sage: %timeit f.reverse() 4.57 ms ± 77 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) sage: %timeit f.inverse_series_trunc(500) 10.9 ms ± 82.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) ``` #### After Patch ```py sage: p = next_prime(2**1024) sage: F.<i> = GF(p^2) sage: R.<x> = PolynomialRing(F, implementation="NTL") sage: sage: f = R([p for p in primes(10)]) sage: %timeit f.reverse() 836 ns ± 9.91 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each) sage: %timeit f.inverse_series_trunc(500) 1.84 ms ± 77.1 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) sage: sage: f = R([p for p in primes(100)]) sage: %timeit f.reverse() 3.63 µs ± 103 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each) sage: %timeit f.inverse_series_trunc(500) 6.66 ms ± 441 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) sage: f = R([p for p in primes(1000)]) sage: %timeit f.reverse() 26.8 µs ± 1.48 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each) sage: %timeit f.inverse_series_trunc(500) 8.75 ms ± 432 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) ``` ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> <!-- If your change requires a documentation PR, please link it appropriately --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> <!-- Feel free to remove irrelevant items. --> - [x] The title is concise, informative, and self-explanatory. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [x] I have updated the documentation accordingly. ### ⌛ Dependencies The only additional import is `sig_on()` and `sig_off()` into the `Polynomial_ZZ_pEX.pyx` ### Warning! This is my first attempt to contribute new functions to sagemath, and it's also my first few weeks of playing with cython. I have done my best to do what is needed, but maybe I have done something incorrect. Please let me know if there's more I can do. In particular, I know an alternative edit to the one I proposed is to modify the child `Polynomial_template` class instead of the `Polynomial_ZZ_pEX` class, but I know less about all the other parents of this class and I was concerned I may make breaking changes somewhere downstream. Currently, this just adds the NTL methods when I know they are avaialble and they simply do what is done currently, but faster. URL: #36097 Reported by: Giacomo Pope Reviewer(s): Frédéric Chapoton
2 parents 53e8cbb + 1308baa commit 48ef6df

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

src/sage/rings/polynomial/polynomial_zz_pex.pyx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ AUTHOR:
1212
1313
- Yann Laigle-Chapuy (2010-01) initial implementation
1414
- Lorenz Panny (2023-01): :meth:`minpoly_mod`
15+
- Giacomo Pope (2023-08): :meth:`reverse`, :meth:`inverse_series_trunc`
1516
"""
17+
from cysignals.signals cimport sig_on, sig_off
18+
1619
from sage.libs.ntl.ntl_ZZ_pEContext cimport ntl_ZZ_pEContext_class
1720
from sage.libs.ntl.ZZ_pE cimport ZZ_pE_to_ZZ_pX
1821
from sage.libs.ntl.ZZ_pX cimport ZZ_pX_deg, ZZ_pX_coeff
@@ -483,3 +486,129 @@ cdef class Polynomial_ZZ_pEX(Polynomial_template):
483486
x^4 + x^3 + x
484487
"""
485488
return self.shift(-n)
489+
490+
def reverse(self, degree=None):
491+
r"""
492+
Return the polynomial obtained by reversing the coefficients
493+
of this polynomial. If degree is set then this function behaves
494+
as if this polynomial has degree ``degree``.
495+
496+
EXAMPLES::
497+
498+
sage: R.<x> = GF(101^2)[]
499+
sage: f = x^13 + 11*x^10 + 32*x^6 + 4
500+
sage: f.reverse()
501+
4*x^13 + 32*x^7 + 11*x^3 + 1
502+
sage: f.reverse(degree=15)
503+
4*x^15 + 32*x^9 + 11*x^5 + x^2
504+
sage: f.reverse(degree=2)
505+
4*x^2
506+
507+
TESTS::
508+
509+
sage: R.<x> = GF(163^2)[]
510+
sage: f = R([p for p in primes(20)])
511+
sage: f.reverse()
512+
2*x^7 + 3*x^6 + 5*x^5 + 7*x^4 + 11*x^3 + 13*x^2 + 17*x + 19
513+
sage: f.reverse(degree=200)
514+
2*x^200 + 3*x^199 + 5*x^198 + 7*x^197 + 11*x^196 + 13*x^195 + 17*x^194 + 19*x^193
515+
sage: f.reverse(degree=0)
516+
Traceback (most recent call last):
517+
...
518+
ValueError: degree argument must be a non-negative integer, got 0
519+
sage: f.reverse(degree=-5)
520+
Traceback (most recent call last):
521+
...
522+
ValueError: degree argument must be a non-negative integer, got -5
523+
"""
524+
self._parent._modulus.restore()
525+
526+
# Construct output polynomial
527+
cdef Polynomial_ZZ_pEX r
528+
r = Polynomial_ZZ_pEX.__new__(Polynomial_ZZ_pEX)
529+
celement_construct(&r.x, (<Polynomial_template>self)._cparent)
530+
r._parent = (<Polynomial_template>self)._parent
531+
r._cparent = (<Polynomial_template>self)._cparent
532+
533+
# When a degree has been supplied, ensure it is a valid input
534+
cdef unsigned long d
535+
if degree is not None:
536+
if degree <= 0:
537+
raise ValueError("degree argument must be a non-negative integer, got %s" % (degree))
538+
try:
539+
d = degree
540+
except ValueError:
541+
raise ValueError("degree argument must be a non-negative integer, got %s" % (degree))
542+
ZZ_pEX_reverse_hi(r.x, (<Polynomial_ZZ_pEX> self).x, d)
543+
else:
544+
ZZ_pEX_reverse(r.x, (<Polynomial_ZZ_pEX> self).x)
545+
return r
546+
547+
def inverse_series_trunc(self, prec):
548+
r"""
549+
Compute and return the inverse of self modulo `x^prec`.
550+
551+
The constant term of self must be invertible.
552+
553+
EXAMPLES::
554+
555+
sage: R.<x> = GF(101^2)[]
556+
sage: z2 = R.base_ring().gen()
557+
sage: f = (3*z2 + 57)*x^3 + (13*z2 + 94)*x^2 + (7*z2 + 2)*x + 66*z2 + 15
558+
sage: f.inverse_series_trunc(1)
559+
51*z2 + 92
560+
sage: f.inverse_series_trunc(2)
561+
(30*z2 + 30)*x + 51*z2 + 92
562+
sage: f.inverse_series_trunc(3)
563+
(42*z2 + 94)*x^2 + (30*z2 + 30)*x + 51*z2 + 92
564+
sage: f.inverse_series_trunc(4)
565+
(99*z2 + 96)*x^3 + (42*z2 + 94)*x^2 + (30*z2 + 30)*x + 51*z2 + 92
566+
567+
TESTS::
568+
569+
sage: R.<x> = GF(163^2)[]
570+
sage: f = R([p for p in primes(20)])
571+
sage: f.inverse_series_trunc(1)
572+
82
573+
sage: f.inverse_series_trunc(2)
574+
40*x + 82
575+
sage: f.inverse_series_trunc(3)
576+
61*x^2 + 40*x + 82
577+
sage: f.inverse_series_trunc(0)
578+
Traceback (most recent call last):
579+
...
580+
ValueError: the precision must be positive, got 0
581+
sage: f.inverse_series_trunc(-1)
582+
Traceback (most recent call last):
583+
...
584+
ValueError: the precision must be positive, got -1
585+
sage: f = x + x^2 + x^3
586+
sage: f.inverse_series_trunc(5)
587+
Traceback (most recent call last):
588+
...
589+
ValueError: constant term 0 is not a unit
590+
"""
591+
self._parent._modulus.restore()
592+
593+
# Ensure precision is non-negative
594+
if prec <= 0:
595+
raise ValueError("the precision must be positive, got {}".format(prec))
596+
597+
# Ensure we can invert the constant term
598+
const_term = self.get_coeff_c(0)
599+
if not const_term.is_unit():
600+
raise ValueError("constant term {} is not a unit".format(const_term))
601+
602+
# Construct output polynomial
603+
cdef Polynomial_ZZ_pEX r
604+
r = Polynomial_ZZ_pEX.__new__(Polynomial_ZZ_pEX)
605+
celement_construct(&r.x, (<Polynomial_template>self)._cparent)
606+
r._parent = (<Polynomial_template>self)._parent
607+
r._cparent = (<Polynomial_template>self)._cparent
608+
609+
# Call to NTL for the inverse truncation
610+
if prec > 0:
611+
sig_on()
612+
ZZ_pEX_InvTrunc(r.x, self.x, prec)
613+
sig_off()
614+
return r

0 commit comments

Comments
 (0)