Skip to content

Commit 075e9e3

Browse files
committed
add compose_mod and powmod with large exp
1 parent 30e71dc commit 075e9e3

File tree

2 files changed

+107
-12
lines changed

2 files changed

+107
-12
lines changed

src/flint/test/test.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2090,6 +2090,18 @@ def test_fmpz_mod_poly():
20902090
assert f*f == f**2
20912091
assert f*f == f**fmpz(2)
20922092

2093+
# powmod
2094+
# assert ui and fmpz exp agree for polynomials and generators
2095+
R_gen = R_test.gen()
2096+
assert pow(f, 2**60, g) == pow(pow(f, 2**30, g), 2**30, g)
2097+
assert pow(R_gen, 2**60, g) == pow(pow(R_gen, 2**30, g), 2**30, g)
2098+
2099+
# Check other typechecks
2100+
assert raises(lambda: pow(f, -2, g), ValueError)
2101+
assert raises(lambda: pow(f, 1, "A"), TypeError)
2102+
assert raises(lambda: pow(f, "A", g), TypeError)
2103+
assert raises(lambda: f.powmod(2**32, g, mod_rev_inv="A"), TypeError)
2104+
20932105
# Shifts
20942106
assert raises(lambda: R_test([1,2,3]).left_shift(-1), ValueError)
20952107
assert raises(lambda: R_test([1,2,3]).right_shift(-1), ValueError)
@@ -2121,6 +2133,13 @@ def test_fmpz_mod_poly():
21212133
# compose
21222134
assert raises(lambda: h.compose("AAA"), TypeError)
21232135

2136+
# compose mod
2137+
mod = R_test([1,2,3,4])
2138+
assert f.compose(h) % mod == f.compose_mod(h, mod)
2139+
assert raises(lambda: h.compose_mod("AAA", mod), TypeError)
2140+
assert raises(lambda: h.compose_mod(f, "AAA"), TypeError)
2141+
assert raises(lambda: h.compose_mod(f, R_test(0)), ZeroDivisionError)
2142+
21242143
# Reverse
21252144
assert raises(lambda: h.reverse(degree=-100), ValueError)
21262145
assert R_test([-1,-2,-3]).reverse() == R_test([-3,-2,-1])
@@ -2606,8 +2625,9 @@ def setbad(obj, i, val):
26062625
assert raises(lambda: P([1, 1]) ** -1, ValueError)
26072626
assert raises(lambda: P([1, 1]) ** None, TypeError)
26082627

2609-
# # XXX: Not sure what this should do in general:
2610-
assert raises(lambda: pow(P([1, 1]), 2, 3), NotImplementedError)
2628+
# XXX: Not sure what this should do in general:
2629+
# TODO: this now fails as fmpz_mod_poly allows modulus
2630+
# assert raises(lambda: pow(P([1, 1]), 2, 3), NotImplementedError)
26112631

26122632
assert P([1, 2, 1]).gcd(P([1, 1])) == P([1, 1])
26132633
assert raises(lambda: P([1, 2, 1]).gcd(None), TypeError)

src/flint/types/fmpz_mod_poly.pyx

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ cdef class fmpz_mod_poly(flint_poly):
538538

539539
def __pow__(self, e, mod=None):
540540
if mod is not None:
541-
raise NotImplementedError
541+
return self.powmod(e, mod)
542542

543543
cdef fmpz_mod_poly res
544544
if e < 0:
@@ -784,11 +784,11 @@ cdef class fmpz_mod_poly(flint_poly):
784784

785785
return evaluations
786786

787-
def compose(self, input):
787+
def compose(self, other):
788788
"""
789789
Returns the composition of two polynomials
790790
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``
792792
by `f(x)`, `g(x)`, returns `f(g(x))`.
793793
794794
>>> R = fmpz_mod_poly_ctx(163)
@@ -800,12 +800,45 @@ cdef class fmpz_mod_poly(flint_poly):
800800
9*x^4 + 12*x^3 + 10*x^2 + 4*x + 1
801801
"""
802802
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)
804804
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}")
806806

807807
res = self.ctx.new_ctype_poly()
808808
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)
809842
return res
810843

811844
cpdef long length(self):
@@ -1110,10 +1143,14 @@ cdef class fmpz_mod_poly(flint_poly):
11101143
)
11111144
return res
11121145

1113-
def powmod(self, e, modulus):
1146+
def powmod(self, e, modulus, mod_rev_inv=None):
11141147
"""
11151148
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.
11171154
11181155
>>> R = fmpz_mod_poly_ctx(163)
11191156
>>> x = R.gen()
@@ -1123,17 +1160,55 @@ cdef class fmpz_mod_poly(flint_poly):
11231160
>>>
11241161
>>> f.powmod(123, mod)
11251162
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
11261168
"""
11271169
cdef fmpz_mod_poly res
11281170

1171+
if e < 0:
1172+
raise ValueError("Exponent must be non-negative")
1173+
11291174
modulus = self.ctx.any_as_fmpz_mod_poly(modulus)
11301175
if modulus is NotImplemented:
11311176
raise TypeError(f"Cannot interpret {modulus} as a polynomial")
11321177

1178+
# Output polynomial
11331179
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+
)
11371212
return res
11381213

11391214
def divmod(self, other):

0 commit comments

Comments
 (0)