@@ -538,7 +538,7 @@ cdef class fmpz_mod_poly(flint_poly):
538
538
539
539
def __pow__ (self , e , mod = None ):
540
540
if mod is not None :
541
- raise NotImplementedError
541
+ return self .powmod(e, mod)
542
542
543
543
cdef fmpz_mod_poly res
544
544
if e < 0 :
@@ -784,11 +784,11 @@ cdef class fmpz_mod_poly(flint_poly):
784
784
785
785
return evaluations
786
786
787
- def compose (self , input ):
787
+ def compose (self , other ):
788
788
"""
789
789
Returns the composition of two polynomials
790
790
791
- To be precise about the order of composition, given ``self``, and ``input ``
791
+ To be precise about the order of composition, given ``self``, and ``other ``
792
792
by `f(x)`, `g(x)`, returns `f(g(x))`.
793
793
794
794
>>> R = fmpz_mod_poly_ctx(163)
@@ -800,12 +800,45 @@ cdef class fmpz_mod_poly(flint_poly):
800
800
9*x^4 + 12*x^3 + 10*x^2 + 4*x + 1
801
801
"""
802
802
cdef fmpz_mod_poly res
803
- val = self .ctx.any_as_fmpz_mod_poly(input )
803
+ val = self .ctx.any_as_fmpz_mod_poly(other )
804
804
if val is NotImplemented :
805
- raise TypeError (f" Cannot compose the polynomial with input: {input }" )
805
+ raise TypeError (f" Cannot compose the polynomial with input: {other }" )
806
806
807
807
res = self .ctx.new_ctype_poly()
808
808
fmpz_mod_poly_compose(res.val, self .val, (< fmpz_mod_poly> val).val, self .ctx.mod.val)
809
+ return res
810
+
811
+ def compose_mod (self , other , modulus ):
812
+ """
813
+ Returns the composition of two polynomials modulo a third.
814
+
815
+ To be precise about the order of composition, given ``self``, and ``other``
816
+ and ``modulus`` by `f(x)`, `g(x)` and `h(x)`, returns `f(g(x)) \mod h(x)`.
817
+ We require that `h(x)` is non-zero.
818
+
819
+ >>> R = fmpz_mod_poly_ctx(163)
820
+ >>> f = R([1,2,3,4,5])
821
+ >>> g = R([3,2,1])
822
+ >>> h = R([1,0,1,0,1])
823
+ >>> f.compose_mod(g, h)
824
+ 63*x^3 + 100*x^2 + 17*x + 63
825
+ >>> g.compose_mod(f, h)
826
+ 147*x^3 + 159*x^2 + 4*x + 7
827
+ """
828
+ cdef fmpz_mod_poly res
829
+ val = self .ctx.any_as_fmpz_mod_poly(other)
830
+ if val is NotImplemented :
831
+ raise TypeError (f" cannot compose the polynomial with input: {other}" )
832
+
833
+ h = self .ctx.any_as_fmpz_mod_poly(modulus)
834
+ if h is NotImplemented :
835
+ raise TypeError (f" cannot reduce the polynomial with input: {modulus}" )
836
+
837
+ if h.is_zero():
838
+ raise ZeroDivisionError (" cannot reduce modulo zero" )
839
+
840
+ res = self .ctx.new_ctype_poly()
841
+ fmpz_mod_poly_compose_mod(res.val, self .val, (< fmpz_mod_poly> val).val, (< fmpz_mod_poly> h).val, self .ctx.mod.val)
809
842
return res
810
843
811
844
cpdef long length(self ):
@@ -1110,10 +1143,14 @@ cdef class fmpz_mod_poly(flint_poly):
1110
1143
)
1111
1144
return res
1112
1145
1113
- def powmod (self , e , modulus ):
1146
+ def powmod (self , e , modulus , mod_rev_inv = None ):
1114
1147
"""
1115
1148
Returns ``self`` raised to the power ``e`` modulo ``modulus``:
1116
- :math:`f^e \mod g`
1149
+ :math:`f^e \mod g`/
1150
+
1151
+ ``mod_rev_inv`` is the inverse of the reverse of the modulus,
1152
+ precomputing it and passing it to ``powmod()`` can optimise
1153
+ powering of polynomials with large exponents.
1117
1154
1118
1155
>>> R = fmpz_mod_poly_ctx(163)
1119
1156
>>> x = R.gen()
@@ -1123,17 +1160,55 @@ cdef class fmpz_mod_poly(flint_poly):
1123
1160
>>>
1124
1161
>>> f.powmod(123, mod)
1125
1162
3*x^3 + 25*x^2 + 115*x + 161
1163
+ >>> f.powmod(2**64, mod)
1164
+ 52*x^3 + 96*x^2 + 136*x + 9
1165
+ >>> mod_rev_inv = mod.reverse().inverse_series_trunc(4)
1166
+ >>> f.powmod(2**64, mod, mod_rev_inv)
1167
+ 52*x^3 + 96*x^2 + 136*x + 9
1126
1168
"""
1127
1169
cdef fmpz_mod_poly res
1128
1170
1171
+ if e < 0 :
1172
+ raise ValueError (" Exponent must be non-negative" )
1173
+
1129
1174
modulus = self .ctx.any_as_fmpz_mod_poly(modulus)
1130
1175
if modulus is NotImplemented :
1131
1176
raise TypeError (f" Cannot interpret {modulus} as a polynomial" )
1132
1177
1178
+ # Output polynomial
1133
1179
res = self .ctx.new_ctype_poly()
1134
- fmpz_mod_poly_powmod_ui_binexp(
1135
- res.val, self .val, < ulong> e, (< fmpz_mod_poly> modulus).val, res.ctx.mod.val
1136
- )
1180
+
1181
+ # For small exponents, use a simple powering method
1182
+ if e.bit_length() < 32 :
1183
+ fmpz_mod_poly_powmod_ui_binexp(
1184
+ res.val, self .val, < ulong> e, (< fmpz_mod_poly> modulus).val, res.ctx.mod.val
1185
+ )
1186
+ return res
1187
+
1188
+ # For larger exponents we can use faster algorithms, first convert exp to fmpz type
1189
+ e_fmpz = any_as_fmpz(e)
1190
+ if e_fmpz is NotImplemented :
1191
+ raise ValueError (f" exponent cannot be cast to an fmpz type: {e = }" )
1192
+
1193
+ # To optimise powering, we precompute the inverse of the reverse of the modulus
1194
+ if mod_rev_inv is not None :
1195
+ mod_rev_inv = self .ctx.any_as_fmpz_mod_poly(mod_rev_inv)
1196
+ if mod_rev_inv is NotImplemented :
1197
+ raise TypeError (f" Cannot interpret {mod_rev_inv} as a polynomial" )
1198
+ else :
1199
+ mod_rev_inv = modulus.reverse().inverse_series_trunc(modulus.length())
1200
+
1201
+ # Use windows exponentiation optimisation when self = x
1202
+ if self .is_gen():
1203
+ fmpz_mod_poly_powmod_x_fmpz_preinv(
1204
+ res.val, (< fmpz> e_fmpz).val, (< fmpz_mod_poly> modulus).val, (< fmpz_mod_poly> mod_rev_inv).val, res.ctx.mod.val
1205
+ )
1206
+ return res
1207
+
1208
+ # Otherwise using binary exponentiation for all other inputs
1209
+ fmpz_mod_poly_powmod_fmpz_binexp_preinv(
1210
+ res.val, self .val, (< fmpz> e_fmpz).val, (< fmpz_mod_poly> modulus).val, (< fmpz_mod_poly> mod_rev_inv).val, res.ctx.mod.val
1211
+ )
1137
1212
return res
1138
1213
1139
1214
def divmod (self , other ):
0 commit comments