Skip to content

Commit bc1e110

Browse files
committed
add the same methods for nmod_poly
1 parent 8f7ae8f commit bc1e110

File tree

4 files changed

+196
-3
lines changed

4 files changed

+196
-3
lines changed

src/flint/flintlib/nmod_poly.pxd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ cdef extern from "flint/nmod_poly.h":
5555
int nmod_poly_equal_trunc(const nmod_poly_t poly1, const nmod_poly_t poly2, slong n)
5656
int nmod_poly_is_zero(const nmod_poly_t poly)
5757
int nmod_poly_is_one(const nmod_poly_t poly)
58+
int nmod_poly_is_gen(const nmod_poly_t poly)
5859
void _nmod_poly_shift_left(mp_ptr res, mp_srcptr poly, slong len, slong k)
5960
void nmod_poly_shift_left(nmod_poly_t res, const nmod_poly_t poly, slong k)
6061
void _nmod_poly_shift_right(mp_ptr res, mp_srcptr poly, slong len, slong k)

src/flint/test/test_all.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1422,7 +1422,15 @@ def test_nmod_poly():
14221422
assert raises(lambda: [] * s, TypeError)
14231423
assert raises(lambda: [] // s, TypeError)
14241424
assert raises(lambda: [] % s, TypeError)
1425-
assert raises(lambda: pow(P([1,2],3), 3, 4), NotImplementedError)
1425+
assert raises(lambda: [] % s, TypeError)
1426+
assert raises(lambda: s.reverse(-1), ValueError)
1427+
assert raises(lambda: s.compose("A"), TypeError)
1428+
assert raises(lambda: s.compose_mod(s, "A"), TypeError)
1429+
assert raises(lambda: s.compose_mod("A", P([3,6,9],17)), TypeError)
1430+
assert raises(lambda: s.compose_mod(s, P([0], 17)), ZeroDivisionError)
1431+
assert raises(lambda: pow(s, -1, P([3,6,9],17)), ValueError)
1432+
assert raises(lambda: pow(s, 1, "A"), TypeError)
1433+
assert raises(lambda: pow(s, "A", P([3,6,9],17)), TypeError)
14261434
assert str(P([1,2,3],17)) == "3*x^2 + 2*x + 1"
14271435
assert P([1,2,3],17).repr() == "nmod_poly([1, 2, 3], 17)"
14281436
p = P([3,4,5],17)

src/flint/types/fmpz_mod_poly.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ cdef class fmpz_mod_poly(flint_poly):
11821182
# For larger exponents we need to cast e to an fmpz first
11831183
e_fmpz = any_as_fmpz(e)
11841184
if e_fmpz is NotImplemented:
1185-
raise ValueError(f"exponent cannot be cast to an fmpz type: {e = }")
1185+
raise TypeError(f"exponent cannot be cast to an fmpz type: {e = }")
11861186

11871187
# To optimise powering, we precompute the inverse of the reverse of the modulus
11881188
if mod_rev_inv is not None:

src/flint/types/nmod_poly.pyx

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from cpython.list cimport PyList_GET_SIZE
22
from flint.flint_base.flint_base cimport flint_poly
33
from flint.utils.typecheck cimport typecheck
4+
from flint.types.fmpz cimport fmpz, any_as_fmpz
45
from flint.types.fmpz_poly cimport any_as_fmpz_poly
56
from flint.types.fmpz_poly cimport fmpz_poly
67
from flint.types.nmod cimport any_as_nmod
@@ -182,6 +183,12 @@ cdef class nmod_poly(flint_poly):
182183
else:
183184
raise TypeError("cannot set element of type %s" % type(x))
184185

186+
def degree(self):
187+
return nmod_poly_degree(self.val)
188+
189+
def length(self):
190+
return nmod_poly_length(self.val)
191+
185192
def __bool__(self):
186193
return not nmod_poly_is_zero(self.val)
187194

@@ -191,6 +198,115 @@ cdef class nmod_poly(flint_poly):
191198
def is_one(self):
192199
return <bint>nmod_poly_is_one(self.val)
193200

201+
def is_gen(self):
202+
return <bint>nmod_poly_is_gen(self.val)
203+
204+
def reverse(self, degree=None):
205+
"""
206+
Return a polynomial with the coefficients of this polynomial
207+
reversed.
208+
209+
If ``degree`` is not None, the output polynomial will be zero-padded
210+
or truncated before being reversed. NOTE: degree must be non-negative.
211+
212+
>>> f = nmod_poly([1,2,3,4,5], 163)
213+
>>> f.reverse()
214+
x^4 + 2*x^3 + 3*x^2 + 4*x + 5
215+
>>> f.reverse(degree=1)
216+
x + 2
217+
>>> f.reverse(degree=100)
218+
x^100 + 2*x^99 + 3*x^98 + 4*x^97 + 5*x^96
219+
"""
220+
cdef nmod_poly res
221+
cdef slong d
222+
223+
if degree is not None:
224+
d = degree
225+
if d != degree or d < 0:
226+
raise ValueError(f"degree argument must be a non-negative integer, got {degree}")
227+
length = d + 1
228+
else:
229+
length = nmod_poly_length(self.val)
230+
231+
res = nmod_poly.__new__(nmod_poly)
232+
nmod_poly_init_preinv(res.val, self.val.mod.n, self.val.mod.ninv)
233+
nmod_poly_reverse(res.val, self.val, length)
234+
return res
235+
236+
def inverse_series_trunc(self, slong n):
237+
"""
238+
Returns the inverse of ``self`` modulo `x^n`.
239+
240+
>>> f = nmod_poly([123, 129, 63, 14, 51, 76, 133], 163)
241+
>>> f.inverse_series_trunc(3)
242+
159*x^2 + 151*x + 110
243+
>>> f.inverse_series_trunc(4)
244+
23*x^3 + 159*x^2 + 151*x + 110
245+
>>> f.inverse_series_trunc(5)
246+
45*x^4 + 23*x^3 + 159*x^2 + 151*x + 110
247+
"""
248+
cdef nmod_poly res
249+
res = nmod_poly.__new__(nmod_poly)
250+
nmod_poly_init_preinv(res.val, self.val.mod.n, self.val.mod.ninv)
251+
nmod_poly_inv_series(res.val, self.val, n)
252+
return res
253+
254+
def compose(self, other):
255+
"""
256+
Returns the composition of two polynomials
257+
258+
To be precise about the order of composition, given ``self``, and ``other``
259+
by `f(x)`, `g(x)`, returns `f(g(x))`.
260+
261+
>>> f = nmod_poly([1,2,3], 163)
262+
>>> g = nmod_poly([0,0,1], 163)
263+
>>> f.compose(g)
264+
3*x^4 + 2*x^2 + 1
265+
>>> g.compose(f)
266+
9*x^4 + 12*x^3 + 10*x^2 + 4*x + 1
267+
"""
268+
cdef nmod_poly res
269+
other = any_as_nmod_poly(other, (<nmod_poly>self).val.mod)
270+
if other is NotImplemented:
271+
raise TypeError("cannot convert input to nmod_poly")
272+
res = nmod_poly.__new__(nmod_poly)
273+
nmod_poly_init_preinv(res.val, self.val.mod.n, self.val.mod.ninv)
274+
nmod_poly_compose(res.val, self.val, (<nmod_poly>other).val)
275+
return res
276+
277+
def compose_mod(self, other, modulus):
278+
"""
279+
Returns the composition of two polynomials modulo a third.
280+
281+
To be precise about the order of composition, given ``self``, and ``other``
282+
and ``modulus`` by `f(x)`, `g(x)` and `h(x)`, returns `f(g(x)) \mod h(x)`.
283+
We require that `h(x)` is non-zero.
284+
285+
>>> f = nmod_poly([1,2,3,4,5], 163)
286+
>>> g = nmod_poly([3,2,1], 163)
287+
>>> h = nmod_poly([1,0,1,0,1], 163)
288+
>>> f.compose_mod(g, h)
289+
63*x^3 + 100*x^2 + 17*x + 63
290+
>>> g.compose_mod(f, h)
291+
147*x^3 + 159*x^2 + 4*x + 7
292+
"""
293+
cdef nmod_poly res
294+
g = any_as_nmod_poly(other, self.val.mod)
295+
if g is NotImplemented:
296+
raise TypeError(f"cannot convert {other = } to nmod_poly")
297+
298+
h = any_as_nmod_poly(modulus, self.val.mod)
299+
if h is NotImplemented:
300+
raise TypeError(f"cannot convert {modulus = } to nmod_poly")
301+
302+
if modulus.is_zero():
303+
raise ZeroDivisionError("cannot reduce modulo zero")
304+
305+
res = nmod_poly.__new__(nmod_poly)
306+
nmod_poly_init_preinv(res.val, self.val.mod.n, self.val.mod.ninv)
307+
nmod_poly_compose_mod(res.val, self.val, (<nmod_poly>other).val, (<nmod_poly>modulus).val)
308+
return res
309+
194310
def __call__(self, other):
195311
cdef mp_limb_t c
196312
if any_as_nmod(&c, other, self.val.mod):
@@ -358,14 +474,82 @@ cdef class nmod_poly(flint_poly):
358474
def __pow__(nmod_poly self, exp, mod):
359475
cdef nmod_poly res
360476
if mod is not None:
361-
raise NotImplementedError("nmod_poly modular exponentiation")
477+
return self.powmod(exp, mod)
362478
if exp < 0:
363479
raise ValueError("negative exponent")
364480
res = nmod_poly.__new__(nmod_poly)
365481
nmod_poly_init_preinv(res.val, (<nmod_poly>self).val.mod.n, (<nmod_poly>self).val.mod.ninv)
366482
nmod_poly_pow(res.val, self.val, <ulong>exp)
367483
return res
368484

485+
def powmod(self, e, modulus, mod_rev_inv=None):
486+
"""
487+
Returns ``self`` raised to the power ``e`` modulo ``modulus``:
488+
:math:`f^e \mod g`/
489+
490+
``mod_rev_inv`` is the inverse of the reverse of the modulus,
491+
precomputing it and passing it to ``powmod()`` can optimise
492+
powering of polynomials with large exponents.
493+
494+
>>> x = nmod_poly([0,1], 163)
495+
>>> f = 30*x**6 + 104*x**5 + 76*x**4 + 33*x**3 + 70*x**2 + 44*x + 65
496+
>>> g = 43*x**6 + 91*x**5 + 77*x**4 + 113*x**3 + 71*x**2 + 132*x + 60
497+
>>> mod = x**4 + 93*x**3 + 78*x**2 + 72*x + 149
498+
>>>
499+
>>> f.powmod(123, mod)
500+
3*x^3 + 25*x^2 + 115*x + 161
501+
>>> f.powmod(2**64, mod)
502+
52*x^3 + 96*x^2 + 136*x + 9
503+
>>> mod_rev_inv = mod.reverse().inverse_series_trunc(4)
504+
>>> f.powmod(2**64, mod, mod_rev_inv)
505+
52*x^3 + 96*x^2 + 136*x + 9
506+
"""
507+
cdef nmod_poly res
508+
509+
if e < 0:
510+
raise ValueError("Exponent must be non-negative")
511+
512+
modulus = any_as_nmod_poly(modulus, (<nmod_poly>self).val.mod)
513+
if modulus is NotImplemented:
514+
raise TypeError("cannot convert input to nmod_poly")
515+
516+
# Output polynomial
517+
res = nmod_poly.__new__(nmod_poly)
518+
nmod_poly_init_preinv(res.val, self.val.mod.n, self.val.mod.ninv)
519+
520+
# For small exponents, use a simple binary exponentiation method
521+
if e.bit_length() < 32:
522+
nmod_poly_powmod_ui_binexp(
523+
res.val, self.val, <ulong>e, (<nmod_poly>modulus).val
524+
)
525+
return res
526+
527+
# For larger exponents we need to cast e to an fmpz first
528+
e_fmpz = any_as_fmpz(e)
529+
if e_fmpz is NotImplemented:
530+
raise TypeError(f"exponent cannot be cast to an fmpz type: {e = }")
531+
532+
# To optimise powering, we precompute the inverse of the reverse of the modulus
533+
if mod_rev_inv is not None:
534+
mod_rev_inv = any_as_nmod_poly(mod_rev_inv, (<nmod_poly>self).val.mod)
535+
if mod_rev_inv is NotImplemented:
536+
raise TypeError(f"Cannot interpret {mod_rev_inv} as a polynomial")
537+
else:
538+
mod_rev_inv = modulus.reverse().inverse_series_trunc(modulus.length())
539+
540+
# Use windowed exponentiation optimisation when self = x
541+
if self.is_gen():
542+
nmod_poly_powmod_x_fmpz_preinv(
543+
res.val, (<fmpz>e_fmpz).val, (<nmod_poly>modulus).val, (<nmod_poly>mod_rev_inv).val
544+
)
545+
return res
546+
547+
# Otherwise using binary exponentiation for all other inputs
548+
nmod_poly_powmod_fmpz_binexp_preinv(
549+
res.val, self.val, (<fmpz>e_fmpz).val, (<nmod_poly>modulus).val, (<nmod_poly>mod_rev_inv).val
550+
)
551+
return res
552+
369553
def gcd(self, other):
370554
"""
371555
Returns the monic greatest common divisor of self and other.

0 commit comments

Comments
 (0)