Skip to content

Commit 7fba457

Browse files
committed
Make all polynomials handle negative powers
Also add is_constant() method to all polynomial types.
1 parent 60a0f5e commit 7fba457

11 files changed

+218
-18
lines changed

src/flint/flint_base/flint_base.pyx

+9-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ from flint.flintlib.types.flint cimport (
66
)
77
from flint.utils.flint_exceptions import DomainError
88
from flint.flintlib.types.mpoly cimport ordering_t
9+
from flint.flintlib.functions.fmpz cimport fmpz_cmp_si
910
from flint.flint_base.flint_context cimport thectx
1011
from flint.utils.typecheck cimport typecheck
1112
cimport libc.stdlib
@@ -725,14 +726,15 @@ cdef class flint_mpoly(flint_elem):
725726
def __pow__(self, other, modulus):
726727
if modulus is not None:
727728
raise NotImplementedError("cannot specify modulus outside of the context")
728-
elif typecheck(other, fmpz):
729-
return self._pow_(other)
730729

731-
other = any_as_fmpz(other)
732-
if other is NotImplemented:
733-
return NotImplemented
734-
elif other < 0:
735-
raise ValueError("cannot raise to a negative power")
730+
if not typecheck(other, fmpz):
731+
other = any_as_fmpz(other)
732+
if other is NotImplemented:
733+
return NotImplemented
734+
735+
if fmpz_cmp_si((<fmpz>other).val, 0) < 0:
736+
self = 1 / self
737+
other = -other
736738

737739
return self._pow_(other)
738740

src/flint/test/test_all.py

+79-5
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def test_fmpz_poly():
402402
assert raises(lambda: [] // Z([1,2]), TypeError)
403403
assert raises(lambda: [] % Z([1,2]), TypeError)
404404
assert raises(lambda: divmod([], Z([1,2])), TypeError)
405-
assert raises(lambda: Z([1,2,3]) ** -1, (OverflowError, ValueError))
405+
assert raises(lambda: Z([1,2,3]) ** -1, DomainError)
406406
assert raises(lambda: Z([1,2,3]) ** Z([1,2]), TypeError)
407407
assert raises(lambda: Z([1,2]) // Z([]), ZeroDivisionError)
408408
assert raises(lambda: Z([]) // Z([]), ZeroDivisionError)
@@ -2109,7 +2109,7 @@ def test_fmpz_mod_poly():
21092109
assert (f + 1) // f == 1
21102110

21112111
# pow
2112-
assert raises(lambda: f**(-2), ValueError)
2112+
assert raises(lambda: f**(-2), DomainError)
21132113
assert f*f == f**2
21142114
assert f*f == f**fmpz(2)
21152115

@@ -2768,7 +2768,7 @@ def setbad(obj, i, val):
27682768
assert P([1, 1]) ** 0 == P([1])
27692769
assert P([1, 1]) ** 1 == P([1, 1])
27702770
assert P([1, 1]) ** 2 == P([1, 2, 1])
2771-
assert raises(lambda: P([1, 1]) ** -1, ValueError)
2771+
assert raises(lambda: P([1, 1]) ** -1, DomainError)
27722772
assert raises(lambda: P([1, 1]) ** None, TypeError)
27732773

27742774
# XXX: Not sure what this should do in general:
@@ -3254,7 +3254,7 @@ def quick_poly():
32543254
(0, 1): 4,
32553255
(0, 0): 1,
32563256
})
3257-
assert raises(lambda: P(ctx=ctx) ** -1, ValueError)
3257+
assert raises(lambda: P(ctx=ctx) ** -1, ZeroDivisionError)
32583258
assert raises(lambda: P(ctx=ctx) ** None, TypeError)
32593259

32603260
# # XXX: Not sure what this should do in general:
@@ -3464,6 +3464,32 @@ def _all_polys_mpolys():
34643464
yield P, S, [x, y], is_field, characteristic
34653465

34663466

3467+
def test_properties_poly_mpoly():
3468+
"""Test is_zero, is_one etc for all polynomials."""
3469+
for P, S, [x, y], is_field, characteristic in _all_polys_mpolys():
3470+
3471+
zero = 0*x
3472+
one = zero + 1
3473+
two = one + 1
3474+
3475+
assert zero.is_zero() is True
3476+
assert one.is_zero() is False
3477+
assert two.is_zero() is False
3478+
assert x.is_zero() is False
3479+
3480+
assert zero.is_one() is False
3481+
assert one.is_one() is True
3482+
assert two.is_one() is False
3483+
assert x.is_one() is False
3484+
3485+
assert zero.is_constant() is True
3486+
assert one.is_constant() is True
3487+
assert two.is_constant() is True
3488+
assert x.is_constant() is False
3489+
3490+
# is_gen?
3491+
3492+
34673493
def test_factor_poly_mpoly():
34683494
"""Test that factor() is consistent across different poly/mpoly types."""
34693495

@@ -3671,6 +3697,52 @@ def factor_sqf(p):
36713697
assert (2*(x+y)).gcd(4*(x+y)**2) == x + y
36723698

36733699

3700+
def test_division_poly_mpoly():
3701+
"""Test that division is consistent across different poly/mpoly types."""
3702+
3703+
Z = flint.fmpz
3704+
3705+
for P, S, [x, y], is_field, characteristic in _all_polys_mpolys():
3706+
3707+
if characteristic != 0 and not characteristic.is_prime():
3708+
# nmod_poly crashes for many operations with non-prime modulus
3709+
# https://github.com/flintlib/python-flint/issues/124
3710+
# so we can't even test it...
3711+
nmod_poly_will_crash = type(x) is flint.nmod_poly
3712+
if nmod_poly_will_crash:
3713+
continue
3714+
3715+
one = x**0 # 1 as a polynomial
3716+
two = one + one
3717+
3718+
if is_field or characteristic == 0:
3719+
assert x / x == x**0 == 1 == one
3720+
assert x / 1 == x / S(1) == x / one == x**1 == x
3721+
assert 1 / one == one**-1 == one**Z(-1) == 1, type(one)
3722+
assert -1 / one == 1 / -one == (-one)**-1 == (-one)**Z(-1) == -one == -1
3723+
assert (-one) ** -2 == (-one)**Z(-2) == one
3724+
assert raises(lambda: 1 / x, DomainError)
3725+
assert raises(lambda: x ** -1, DomainError)
3726+
3727+
if is_field:
3728+
half = S(1)/2 * one # 1/2 as a polynomial
3729+
assert half == S(1)/2
3730+
assert x / half == 2*x
3731+
assert 1 / half == S(1) / half == one / half == one / (S(1)/2) == 2
3732+
assert half ** -1 == half ** Z(-1) == 2
3733+
assert two ** -1 == two ** Z(-1) == half
3734+
elif characteristic == 0:
3735+
assert raises(lambda: x / 2, DomainError)
3736+
assert raises(lambda: x / two, DomainError), characteristic
3737+
assert raises(lambda: two ** -1, DomainError)
3738+
assert raises(lambda: two ** Z(-1), DomainError)
3739+
else:
3740+
# Non-prime modulus...
3741+
# nmod can crash and fmpz_mod_poly won't crash but has awkward
3742+
# behaviour under division.
3743+
pass
3744+
3745+
36743746
def _all_matrices():
36753747
"""Return a list of matrix types and scalar types."""
36763748
R163 = flint.fmpz_mod_ctx(163)
@@ -4569,7 +4641,7 @@ def test_fq_default_poly():
45694641
# pow
45704642
# assert ui and fmpz exp agree for polynomials and generators
45714643
R_gen = R_test.gen()
4572-
assert raises(lambda: f**(-2), ValueError)
4644+
assert raises(lambda: f**(-2), DomainError)
45734645
assert pow(f, 2**60, g) == pow(pow(f, 2**30, g), 2**30, g)
45744646
assert pow(R_gen, 2**60, g) == pow(pow(R_gen, 2**30, g), 2**30, g)
45754647
assert raises(lambda: pow(f, -2, g), ValueError)
@@ -4698,7 +4770,9 @@ def test_all_tests():
46984770
test_division_poly,
46994771
test_division_matrix,
47004772

4773+
test_properties_poly_mpoly,
47014774
test_factor_poly_mpoly,
4775+
test_division_poly_mpoly,
47024776

47034777
test_polys,
47044778
test_mpolys,

src/flint/types/fmpq_mpoly.pyx

+13
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,19 @@ cdef class fmpq_mpoly(flint_mpoly):
275275
def is_one(self):
276276
return <bint>fmpq_mpoly_is_one(self.val, self.ctx.val)
277277

278+
def is_constant(self):
279+
"""
280+
Returns True if this is a constant polynomial.
281+
282+
>>> R = fmpq_mpoly_ctx.get(['x', 'y'])
283+
>>> x, y = R.gens()
284+
>>> x.is_constant()
285+
False
286+
>>> (0*x + 1).is_constant()
287+
True
288+
"""
289+
return self.total_degree() <= 0
290+
278291
def __richcmp__(self, other, int op):
279292
if not (op == Py_EQ or op == Py_NE):
280293
return NotImplemented

src/flint/types/fmpq_poly.pyx

+20-1
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,29 @@ cdef class fmpq_poly(flint_poly):
171171
return not fmpq_poly_is_zero(self.val)
172172

173173
def is_zero(self):
174+
"""
175+
Returns True if this is the zero polynomial.
176+
"""
174177
return <bint>fmpq_poly_is_zero(self.val)
175178

176179
def is_one(self):
180+
"""
181+
Returns True if this polynomial is equal to 1.
182+
"""
177183
return <bint>fmpq_poly_is_one(self.val)
178184

185+
def is_constant(self):
186+
"""
187+
Returns True if this polynomial is a scalar (constant).
188+
189+
>>> f = fmpq_poly([0, 1])
190+
>>> f
191+
x
192+
>>> f.is_constant()
193+
False
194+
"""
195+
return fmpq_poly_degree(self.val) <= 0
196+
179197
def leading_coefficient(self):
180198
"""
181199
Returns the leading coefficient of the polynomial.
@@ -374,7 +392,8 @@ cdef class fmpq_poly(flint_poly):
374392
if mod is not None:
375393
raise NotImplementedError("fmpz_poly modular exponentiation")
376394
if exp < 0:
377-
raise ValueError("fmpq_poly negative exponent")
395+
self = 1 / self
396+
exp = -exp
378397
res = fmpq_poly.__new__(fmpq_poly)
379398
fmpq_poly_pow(res.val, self.val, <ulong>exp)
380399
return res

src/flint/types/fmpz_mod_mpoly.pyx

+13
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,19 @@ cdef class fmpz_mod_mpoly(flint_mpoly):
296296
def is_one(self):
297297
return <bint>fmpz_mod_mpoly_is_one(self.val, self.ctx.val)
298298

299+
def is_constant(self):
300+
"""
301+
Returns True if this is a constant polynomial.
302+
303+
>>> R = fmpz_mod_mpoly_ctx.get(['x', 'y'], modulus=11)
304+
>>> x, y = R.gens()
305+
>>> x.is_constant()
306+
False
307+
>>> (0*x + 1).is_constant()
308+
True
309+
"""
310+
return self.total_degree() <= 0
311+
299312
def __richcmp__(self, other, int op):
300313
if not (op == Py_EQ or op == Py_NE):
301314
return NotImplemented

src/flint/types/fmpz_mod_poly.pyx

+2-1
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,8 @@ cdef class fmpz_mod_poly(flint_poly):
544544

545545
cdef fmpz_mod_poly res
546546
if e < 0:
547-
raise ValueError("Exponent must be non-negative")
547+
self = 1 / self
548+
e = -e
548549

549550
cdef ulong e_ulong = e
550551
res = self.ctx.new_ctype_poly()

src/flint/types/fmpz_mpoly.pyx

+14
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,20 @@ cdef class fmpz_mpoly(flint_mpoly):
261261
def is_one(self):
262262
return <bint>fmpz_mpoly_is_one(self.val, self.ctx.val)
263263

264+
def is_constant(self):
265+
"""
266+
Returns True if this is a constant polynomial.
267+
268+
>>> ctx = fmpz_mpoly_ctx.get(['x', 'y'])
269+
>>> x, y = ctx.gens()
270+
>>> p = x**2 + y
271+
>>> p.is_constant()
272+
False
273+
>>> (0*p + 1).is_constant()
274+
True
275+
"""
276+
return self.total_degree() <= 0
277+
264278
def __richcmp__(self, other, int op):
265279
if not (op == Py_EQ or op == Py_NE):
266280
return NotImplemented

src/flint/types/fmpz_poly.pyx

+28-1
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,36 @@ cdef class fmpz_poly(flint_poly):
141141
return not fmpz_poly_is_zero(self.val)
142142

143143
def is_zero(self):
144+
"""
145+
True if this polynomial is the zero polynomial.
146+
147+
>>> fmpz_poly([]).is_zero()
148+
True
149+
"""
144150
return <bint>fmpz_poly_is_zero(self.val)
145151

146152
def is_one(self):
153+
"""
154+
True if this polynomial is equal to one.
155+
156+
>>> fmpz_poly([2]).is_one()
157+
False
158+
"""
147159
return <bint>fmpz_poly_is_one(self.val)
148160

161+
def is_constant(self):
162+
"""
163+
True if this is a constant polynomial.
164+
165+
>>> x = fmpz_poly([0, 1])
166+
>>> two = fmpz_poly([2])
167+
>>> x.is_constant()
168+
False
169+
>>> two.is_constant()
170+
True
171+
"""
172+
return fmpz_poly_degree(self.val) <= 0
173+
149174
def leading_coefficient(self):
150175
"""
151176
Returns the leading coefficient of the polynomial.
@@ -348,7 +373,9 @@ cdef class fmpz_poly(flint_poly):
348373
if mod is not None:
349374
raise NotImplementedError("fmpz_poly modular exponentiation")
350375
if exp < 0:
351-
raise ValueError("fmpz_poly negative exponent")
376+
if not fmpz_poly_is_unit(self.val):
377+
raise DomainError("fmpz_poly negative exponent, non-unit base")
378+
exp = -exp
352379
res = fmpz_poly.__new__(fmpz_poly)
353380
fmpz_poly_pow(res.val, self.val, <ulong>exp)
354381
return res

src/flint/types/fq_default_poly.pyx

+2-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,8 @@ cdef class fq_default_poly(flint_poly):
642642

643643
cdef fq_default_poly res
644644
if e < 0:
645-
raise ValueError("Exponent must be non-negative")
645+
self = 1 / self
646+
e = -e
646647

647648
if e == 2:
648649
return self.square()

src/flint/types/nmod_mpoly.pyx

+8-1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ cdef class nmod_mpoly(flint_mpoly):
275275
def is_one(self):
276276
return <bint>nmod_mpoly_is_one(self.val, self.ctx.val)
277277

278+
def is_constant(self):
279+
"""
280+
Returns True if this is a constant polynomial.
281+
"""
282+
return self.total_degree() <= 0
283+
278284
def __richcmp__(self, other, int op):
279285
if not (op == Py_EQ or op == Py_NE):
280286
return NotImplemented
@@ -294,7 +300,8 @@ cdef class nmod_mpoly(flint_mpoly):
294300
self.ctx.val
295301
)
296302
elif isinstance(other, int):
297-
return (op == Py_NE) ^ <bint>nmod_mpoly_equal_ui(self.val, other, self.ctx.val)
303+
omod = other % self.ctx.modulus()
304+
return (op == Py_NE) ^ <bint>nmod_mpoly_equal_ui(self.val, omod, self.ctx.val)
298305
else:
299306
return NotImplemented
300307

0 commit comments

Comments
 (0)