11from cpython.list cimport PyList_GET_SIZE
22from flint.flint_base.flint_base cimport flint_poly
33from flint.utils.typecheck cimport typecheck
4+ from flint.types.fmpz cimport fmpz, any_as_fmpz
45from flint.types.fmpz_poly cimport any_as_fmpz_poly
56from flint.types.fmpz_poly cimport fmpz_poly
67from 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