Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make all polynomials handle negative powers #269

Merged
merged 1 commit into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/flint/flint_base/flint_base.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ from flint.flintlib.types.flint cimport (
)
from flint.utils.flint_exceptions import DomainError
from flint.flintlib.types.mpoly cimport ordering_t
from flint.flintlib.functions.fmpz cimport fmpz_cmp_si
from flint.flint_base.flint_context cimport thectx
from flint.utils.typecheck cimport typecheck
cimport libc.stdlib
Expand Down Expand Up @@ -725,14 +726,15 @@ cdef class flint_mpoly(flint_elem):
def __pow__(self, other, modulus):
if modulus is not None:
raise NotImplementedError("cannot specify modulus outside of the context")
elif typecheck(other, fmpz):
return self._pow_(other)

other = any_as_fmpz(other)
if other is NotImplemented:
return NotImplemented
elif other < 0:
raise ValueError("cannot raise to a negative power")
if not typecheck(other, fmpz):
other = any_as_fmpz(other)
if other is NotImplemented:
return NotImplemented

if fmpz_cmp_si((<fmpz>other).val, 0) < 0:
self = 1 / self
other = -other

return self._pow_(other)

Expand Down
84 changes: 79 additions & 5 deletions src/flint/test/test_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def test_fmpz_poly():
assert raises(lambda: [] // Z([1,2]), TypeError)
assert raises(lambda: [] % Z([1,2]), TypeError)
assert raises(lambda: divmod([], Z([1,2])), TypeError)
assert raises(lambda: Z([1,2,3]) ** -1, (OverflowError, ValueError))
assert raises(lambda: Z([1,2,3]) ** -1, DomainError)
assert raises(lambda: Z([1,2,3]) ** Z([1,2]), TypeError)
assert raises(lambda: Z([1,2]) // Z([]), ZeroDivisionError)
assert raises(lambda: Z([]) // Z([]), ZeroDivisionError)
Expand Down Expand Up @@ -2109,7 +2109,7 @@ def test_fmpz_mod_poly():
assert (f + 1) // f == 1

# pow
assert raises(lambda: f**(-2), ValueError)
assert raises(lambda: f**(-2), DomainError)
assert f*f == f**2
assert f*f == f**fmpz(2)

Expand Down Expand Up @@ -2768,7 +2768,7 @@ def setbad(obj, i, val):
assert P([1, 1]) ** 0 == P([1])
assert P([1, 1]) ** 1 == P([1, 1])
assert P([1, 1]) ** 2 == P([1, 2, 1])
assert raises(lambda: P([1, 1]) ** -1, ValueError)
assert raises(lambda: P([1, 1]) ** -1, DomainError)
assert raises(lambda: P([1, 1]) ** None, TypeError)

# XXX: Not sure what this should do in general:
Expand Down Expand Up @@ -3254,7 +3254,7 @@ def quick_poly():
(0, 1): 4,
(0, 0): 1,
})
assert raises(lambda: P(ctx=ctx) ** -1, ValueError)
assert raises(lambda: P(ctx=ctx) ** -1, ZeroDivisionError)
assert raises(lambda: P(ctx=ctx) ** None, TypeError)

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


def test_properties_poly_mpoly():
"""Test is_zero, is_one etc for all polynomials."""
for P, S, [x, y], is_field, characteristic in _all_polys_mpolys():

zero = 0*x
one = zero + 1
two = one + 1

assert zero.is_zero() is True
assert one.is_zero() is False
assert two.is_zero() is False
assert x.is_zero() is False

assert zero.is_one() is False
assert one.is_one() is True
assert two.is_one() is False
assert x.is_one() is False

assert zero.is_constant() is True
assert one.is_constant() is True
assert two.is_constant() is True
assert x.is_constant() is False

# is_gen?


def test_factor_poly_mpoly():
"""Test that factor() is consistent across different poly/mpoly types."""

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


def test_division_poly_mpoly():
"""Test that division is consistent across different poly/mpoly types."""

Z = flint.fmpz

for P, S, [x, y], is_field, characteristic in _all_polys_mpolys():

if characteristic != 0 and not characteristic.is_prime():
# nmod_poly crashes for many operations with non-prime modulus
# https://github.com/flintlib/python-flint/issues/124
# so we can't even test it...
nmod_poly_will_crash = type(x) is flint.nmod_poly
if nmod_poly_will_crash:
continue

one = x**0 # 1 as a polynomial
two = one + one

if is_field or characteristic == 0:
assert x / x == x**0 == 1 == one
assert x / 1 == x / S(1) == x / one == x**1 == x
assert 1 / one == one**-1 == one**Z(-1) == 1, type(one)
assert -1 / one == 1 / -one == (-one)**-1 == (-one)**Z(-1) == -one == -1
assert (-one) ** -2 == (-one)**Z(-2) == one
assert raises(lambda: 1 / x, DomainError)
assert raises(lambda: x ** -1, DomainError)

if is_field:
half = S(1)/2 * one # 1/2 as a polynomial
assert half == S(1)/2
assert x / half == 2*x
assert 1 / half == S(1) / half == one / half == one / (S(1)/2) == 2
assert half ** -1 == half ** Z(-1) == 2
assert two ** -1 == two ** Z(-1) == half
elif characteristic == 0:
assert raises(lambda: x / 2, DomainError)
assert raises(lambda: x / two, DomainError), characteristic
assert raises(lambda: two ** -1, DomainError)
assert raises(lambda: two ** Z(-1), DomainError)
else:
# Non-prime modulus...
# nmod can crash and fmpz_mod_poly won't crash but has awkward
# behaviour under division.
pass


def _all_matrices():
"""Return a list of matrix types and scalar types."""
R163 = flint.fmpz_mod_ctx(163)
Expand Down Expand Up @@ -4569,7 +4641,7 @@ def test_fq_default_poly():
# pow
# assert ui and fmpz exp agree for polynomials and generators
R_gen = R_test.gen()
assert raises(lambda: f**(-2), ValueError)
assert raises(lambda: f**(-2), DomainError)
assert pow(f, 2**60, g) == pow(pow(f, 2**30, g), 2**30, g)
assert pow(R_gen, 2**60, g) == pow(pow(R_gen, 2**30, g), 2**30, g)
assert raises(lambda: pow(f, -2, g), ValueError)
Expand Down Expand Up @@ -4698,7 +4770,9 @@ def test_all_tests():
test_division_poly,
test_division_matrix,

test_properties_poly_mpoly,
test_factor_poly_mpoly,
test_division_poly_mpoly,

test_polys,
test_mpolys,
Expand Down
13 changes: 13 additions & 0 deletions src/flint/types/fmpq_mpoly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,19 @@ cdef class fmpq_mpoly(flint_mpoly):
def is_one(self):
return <bint>fmpq_mpoly_is_one(self.val, self.ctx.val)

def is_constant(self):
"""
Returns True if this is a constant polynomial.

>>> R = fmpq_mpoly_ctx.get(['x', 'y'])
>>> x, y = R.gens()
>>> x.is_constant()
False
>>> (0*x + 1).is_constant()
True
"""
return self.total_degree() <= 0

def __richcmp__(self, other, int op):
if not (op == Py_EQ or op == Py_NE):
return NotImplemented
Expand Down
21 changes: 20 additions & 1 deletion src/flint/types/fmpq_poly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,29 @@ cdef class fmpq_poly(flint_poly):
return not fmpq_poly_is_zero(self.val)

def is_zero(self):
"""
Returns True if this is the zero polynomial.
"""
return <bint>fmpq_poly_is_zero(self.val)

def is_one(self):
"""
Returns True if this polynomial is equal to 1.
"""
return <bint>fmpq_poly_is_one(self.val)

def is_constant(self):
"""
Returns True if this polynomial is a scalar (constant).

>>> f = fmpq_poly([0, 1])
>>> f
x
>>> f.is_constant()
False
"""
return fmpq_poly_degree(self.val) <= 0

def leading_coefficient(self):
"""
Returns the leading coefficient of the polynomial.
Expand Down Expand Up @@ -374,7 +392,8 @@ cdef class fmpq_poly(flint_poly):
if mod is not None:
raise NotImplementedError("fmpz_poly modular exponentiation")
if exp < 0:
raise ValueError("fmpq_poly negative exponent")
self = 1 / self
exp = -exp
res = fmpq_poly.__new__(fmpq_poly)
fmpq_poly_pow(res.val, self.val, <ulong>exp)
return res
Expand Down
13 changes: 13 additions & 0 deletions src/flint/types/fmpz_mod_mpoly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,19 @@ cdef class fmpz_mod_mpoly(flint_mpoly):
def is_one(self):
return <bint>fmpz_mod_mpoly_is_one(self.val, self.ctx.val)

def is_constant(self):
"""
Returns True if this is a constant polynomial.

>>> R = fmpz_mod_mpoly_ctx.get(['x', 'y'], modulus=11)
>>> x, y = R.gens()
>>> x.is_constant()
False
>>> (0*x + 1).is_constant()
True
"""
return self.total_degree() <= 0

def __richcmp__(self, other, int op):
if not (op == Py_EQ or op == Py_NE):
return NotImplemented
Expand Down
3 changes: 2 additions & 1 deletion src/flint/types/fmpz_mod_poly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,8 @@ cdef class fmpz_mod_poly(flint_poly):

cdef fmpz_mod_poly res
if e < 0:
raise ValueError("Exponent must be non-negative")
self = 1 / self
e = -e

cdef ulong e_ulong = e
res = self.ctx.new_ctype_poly()
Expand Down
14 changes: 14 additions & 0 deletions src/flint/types/fmpz_mpoly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,20 @@ cdef class fmpz_mpoly(flint_mpoly):
def is_one(self):
return <bint>fmpz_mpoly_is_one(self.val, self.ctx.val)

def is_constant(self):
"""
Returns True if this is a constant polynomial.

>>> ctx = fmpz_mpoly_ctx.get(['x', 'y'])
>>> x, y = ctx.gens()
>>> p = x**2 + y
>>> p.is_constant()
False
>>> (0*p + 1).is_constant()
True
"""
return self.total_degree() <= 0

def __richcmp__(self, other, int op):
if not (op == Py_EQ or op == Py_NE):
return NotImplemented
Expand Down
29 changes: 28 additions & 1 deletion src/flint/types/fmpz_poly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,36 @@ cdef class fmpz_poly(flint_poly):
return not fmpz_poly_is_zero(self.val)

def is_zero(self):
"""
True if this polynomial is the zero polynomial.

>>> fmpz_poly([]).is_zero()
True
"""
return <bint>fmpz_poly_is_zero(self.val)

def is_one(self):
"""
True if this polynomial is equal to one.

>>> fmpz_poly([2]).is_one()
False
"""
return <bint>fmpz_poly_is_one(self.val)

def is_constant(self):
"""
True if this is a constant polynomial.

>>> x = fmpz_poly([0, 1])
>>> two = fmpz_poly([2])
>>> x.is_constant()
False
>>> two.is_constant()
True
"""
return fmpz_poly_degree(self.val) <= 0

def leading_coefficient(self):
"""
Returns the leading coefficient of the polynomial.
Expand Down Expand Up @@ -348,7 +373,9 @@ cdef class fmpz_poly(flint_poly):
if mod is not None:
raise NotImplementedError("fmpz_poly modular exponentiation")
if exp < 0:
raise ValueError("fmpz_poly negative exponent")
if not fmpz_poly_is_unit(self.val):
raise DomainError("fmpz_poly negative exponent, non-unit base")
exp = -exp
res = fmpz_poly.__new__(fmpz_poly)
fmpz_poly_pow(res.val, self.val, <ulong>exp)
return res
Expand Down
3 changes: 2 additions & 1 deletion src/flint/types/fq_default_poly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,8 @@ cdef class fq_default_poly(flint_poly):

cdef fq_default_poly res
if e < 0:
raise ValueError("Exponent must be non-negative")
self = 1 / self
e = -e

if e == 2:
return self.square()
Expand Down
9 changes: 8 additions & 1 deletion src/flint/types/nmod_mpoly.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ cdef class nmod_mpoly(flint_mpoly):
def is_one(self):
return <bint>nmod_mpoly_is_one(self.val, self.ctx.val)

def is_constant(self):
"""
Returns True if this is a constant polynomial.
"""
return self.total_degree() <= 0

def __richcmp__(self, other, int op):
if not (op == Py_EQ or op == Py_NE):
return NotImplemented
Expand All @@ -294,7 +300,8 @@ cdef class nmod_mpoly(flint_mpoly):
self.ctx.val
)
elif isinstance(other, int):
return (op == Py_NE) ^ <bint>nmod_mpoly_equal_ui(self.val, other, self.ctx.val)
omod = other % self.ctx.modulus()
return (op == Py_NE) ^ <bint>nmod_mpoly_equal_ui(self.val, omod, self.ctx.val)
else:
return NotImplemented

Expand Down
Loading
Loading