diff --git a/src/flint/flint_base/flint_base.pyx b/src/flint/flint_base/flint_base.pyx index e0b02fcf..4b9ab56d 100644 --- a/src/flint/flint_base/flint_base.pyx +++ b/src/flint/flint_base/flint_base.pyx @@ -4,6 +4,7 @@ from flint.flintlib.flint cimport ( __FLINT_RELEASE as _FLINT_RELEASE, slong ) +from flint.utils.flint_exceptions import DomainError from flint.flintlib.mpoly cimport ordering_t from flint.flint_base.flint_context cimport thectx from flint.flint_base.flint_base cimport Ordering @@ -249,9 +250,13 @@ cdef class flint_poly(flint_elem): roots = [] factors = self.factor() for fac, m in factors[1]: - if fac.degree() == fac[1] == 1: - v = - fac[0] - roots.append((v, m)) + if fac.degree() == 1: + try: + v = - fac[0] / fac[1] + except DomainError: + pass + else: + roots.append((v, m)) return roots def complex_roots(self): diff --git a/src/flint/flintlib/nmod_poly_factor.pxd b/src/flint/flintlib/nmod_poly_factor.pxd index 1b7dda89..426ea148 100644 --- a/src/flint/flintlib/nmod_poly_factor.pxd +++ b/src/flint/flintlib/nmod_poly_factor.pxd @@ -6,9 +6,9 @@ cdef extern from "flint/nmod_poly_factor.h": ctypedef struct nmod_poly_factor_struct: nmod_poly_struct *p - long *exp - long num - long alloc + slong *exp + slong num + slong alloc ctypedef nmod_poly_factor_struct nmod_poly_factor_t[1] # from here on is parsed diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 783a4084..60ccdc02 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -285,7 +285,7 @@ def test_fmpz_factor(): (1296814508839693536173209832765271992846610925502473758289451540212712414540699659186801, 1)] def test_fmpz_functions(): - T, F, VE, OE = True, False, ValueError, OverflowError + T, F, VE, OE, DE = True, False, ValueError, OverflowError, DomainError cases = [ # (f, [f(-1), f(0), f(1), f(2), ... f(10)]), (lambda n: flint.fmpz(n).is_prime(), @@ -331,11 +331,11 @@ def test_fmpz_functions(): (lambda n: flint.fmpz(n).euler_phi(), [0, 0, 1, 1, 2, 2, 4, 2, 6, 4, 6, 4]), (lambda n: flint.fmpz(n).isqrt(), - [VE, 0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3]), + [DE, 0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3]), (lambda n: flint.fmpz(n).sqrtrem(), - [VE, (0, 0), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 0), (3, 1)]), + [DE, (0, 0), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 0), (3, 1)]), (lambda n: flint.fmpz(n).sqrtmod(11), - [VE, 0, 1, VE, 5, 2, 4, VE, VE, VE, 3, VE]), + [DE, 0, 1, DE, 5, 2, 4, DE, DE, DE, 3, DE]), (lambda n: flint.fmpz(n).root(3), [VE, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2]), (lambda n: flint.fmpz(n).jacobi(3), @@ -454,7 +454,7 @@ def test_fmpz_poly(): assert Z([1,2,-4]).height_bits() == 3 assert Z([1,2,-4]).height_bits(signed=True) == -3 assert Z([1,2,1]).sqrt() == Z([1,1]) - assert raises(lambda: Z([1,2,2]).sqrt(), ValueError) + assert raises(lambda: Z([1,2,2]).sqrt(), DomainError) assert Z([1,0,2,0,3]).deflation() == (Z([1,2,3]), 2) assert Z([]).deflation() == (Z([]), 1) assert Z([1,1]).deflation() == (Z([1,1]), 1) @@ -2072,14 +2072,14 @@ def test_fmpz_mod_poly(): # true div assert raises(lambda: f / "AAA", TypeError) assert raises(lambda: f / 0, ZeroDivisionError) - assert raises(lambda: f_cmp / 2, ZeroDivisionError) + assert raises(lambda: f_cmp / 2, DomainError) assert (f + f) / 2 == f assert (f + f) / fmpz(2) == f assert (f + f) / F_test(2) == f # floor div - assert raises(lambda: 1 // f_bad, ZeroDivisionError) + assert raises(lambda: 1 // f_bad, DomainError) assert raises(lambda: f // f_cmp, ValueError) assert raises(lambda: f // "AAA", TypeError) assert raises(lambda: "AAA" // f, TypeError) @@ -2115,7 +2115,7 @@ def test_fmpz_mod_poly(): # Mod assert raises(lambda: f % f_bad, ValueError) - assert raises(lambda: 123 % f_bad, ValueError) + assert raises(lambda: 123 % f_bad, DomainError) assert raises(lambda: f % "AAA", TypeError) assert raises(lambda: tuple() % f, TypeError), f'{"AAA" % f = }' @@ -2173,7 +2173,7 @@ def test_fmpz_mod_poly(): assert raises(lambda: f_bad.divmod(f_bad), ValueError) # gcd - assert raises(lambda: f_cmp.gcd(f_cmp), NotImplementedError) + assert raises(lambda: f_cmp.gcd(f_cmp), DomainError) assert raises(lambda: f.gcd("f"), TypeError) # xgcd @@ -2204,7 +2204,7 @@ def test_fmpz_mod_poly(): # sqrt f1 = R_test.random_element(irreducible=True) - assert raises(lambda: f1.sqrt(), ValueError) + assert raises(lambda: f1.sqrt(), DomainError) assert (f1*f1).sqrt() in [f1, -f1] # deflation @@ -2229,8 +2229,8 @@ def test_fmpz_mod_poly(): assert raises(lambda: R_test([0,0,1]).complex_roots(), DomainError) # composite moduli not supported - assert raises(lambda: R_cmp([0,0,1]).factor(), NotImplementedError) - assert raises(lambda: R_cmp([0,0,1]).factor_squarefree(), NotImplementedError) + assert raises(lambda: R_cmp([0,0,1]).factor(), DomainError) + assert raises(lambda: R_cmp([0,0,1]).factor_squarefree(), DomainError) assert raises(lambda: R_cmp([0,0,1]).roots(), NotImplementedError) assert raises(lambda: R_cmp([0,0,1]).complex_roots(), DomainError) @@ -2480,42 +2480,67 @@ def test_division_matrix(): def _all_polys(): return [ - # (poly_type, scalar_type, is_field) - (flint.fmpz_poly, flint.fmpz, False), - (flint.fmpq_poly, flint.fmpq, True), - (lambda *a: flint.nmod_poly(*a, 17), lambda x: flint.nmod(x, 17), True), + # (poly_type, scalar_type, is_field, characteristic) + + # Z and Q + (flint.fmpz_poly, flint.fmpz, False, flint.fmpz(0)), + (flint.fmpq_poly, flint.fmpq, True, flint.fmpz(0)), + + # Z/pZ (p prime) + (lambda *a: flint.nmod_poly(*a, 17), lambda x: flint.nmod(x, 17), True, flint.fmpz(17)), (lambda *a: flint.fmpz_mod_poly(*a, flint.fmpz_mod_poly_ctx(163)), lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(163)), - True), + True, flint.fmpz(163)), (lambda *a: flint.fmpz_mod_poly(*a, flint.fmpz_mod_poly_ctx(2**127 - 1)), lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(2**127 - 1)), - True), + True, flint.fmpz(2**127 - 1)), (lambda *a: flint.fmpz_mod_poly(*a, flint.fmpz_mod_poly_ctx(2**255 - 19)), lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(2**255 - 19)), - True), + True, flint.fmpz(2**255 - 19)), + + # GF(p^k) (p prime) (lambda *a: flint.fq_default_poly(*a, flint.fq_default_poly_ctx(2**127 - 1)), lambda x: flint.fq_default(x, flint.fq_default_ctx(2**127 - 1)), - True), + True, flint.fmpz(2**127 - 1)), (lambda *a: flint.fq_default_poly(*a, flint.fq_default_poly_ctx(2**127 - 1, 2)), lambda x: flint.fq_default(x, flint.fq_default_ctx(2**127 - 1, 2)), - True), + True, flint.fmpz(2**127 - 1)), (lambda *a: flint.fq_default_poly(*a, flint.fq_default_poly_ctx(65537)), lambda x: flint.fq_default(x, flint.fq_default_ctx(65537)), - True), + True, flint.fmpz(65537)), (lambda *a: flint.fq_default_poly(*a, flint.fq_default_poly_ctx(65537, 5)), lambda x: flint.fq_default(x, flint.fq_default_ctx(65537, 5)), - True), + True, flint.fmpz(65537)), (lambda *a: flint.fq_default_poly(*a, flint.fq_default_poly_ctx(11)), lambda x: flint.fq_default(x, flint.fq_default_ctx(11)), - True), + True, flint.fmpz(11)), (lambda *a: flint.fq_default_poly(*a, flint.fq_default_poly_ctx(11, 5)), lambda x: flint.fq_default(x, flint.fq_default_ctx(11, 5)), - True), + True, flint.fmpz(11)), + + # Z/nZ (n composite) + (lambda *a: flint.nmod_poly(*a, 16), lambda x: flint.nmod(x, 16), False, flint.fmpz(16)), + (lambda *a: flint.fmpz_mod_poly(*a, flint.fmpz_mod_poly_ctx(164)), + lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(164)), + False, flint.fmpz(164)), + (lambda *a: flint.fmpz_mod_poly(*a, flint.fmpz_mod_poly_ctx(2**127)), + lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(2**127)), + False, flint.fmpz(2**127)), + (lambda *a: flint.fmpz_mod_poly(*a, flint.fmpz_mod_poly_ctx(2**255)), + lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(2**255)), + False, flint.fmpz(2**255)), ] def test_polys(): - for P, S, is_field in _all_polys(): + for P, S, is_field, characteristic in _all_polys(): + + composite_characteristic = 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(P(1)) is flint.nmod_poly and composite_characteristic + assert P([S(1)]) == P([1]) == P(P([1])) == P(1) assert raises(lambda: P([None]), TypeError) @@ -2601,7 +2626,7 @@ def setbad(obj, i, val): elif type(p) == flint.fmpq_poly: assert P(v).repr() == f'fmpq_poly({v!r})' elif type(p) == flint.nmod_poly: - assert P(v).repr() == f'nmod_poly({v!r}, 17)' + assert P(v).repr() == f'nmod_poly({v!r}, {p.modulus()})' elif type(p) == flint.fmpz_mod_poly: pass # fmpz_mod_poly does not have .repr() ... elif type(p) == flint.fq_default_poly: @@ -2666,9 +2691,18 @@ def setbad(obj, i, val): if is_field: assert P([1, 1]) // 2 == P([S(1)/2, S(1)/2]) assert P([1, 1]) % 2 == P([0]) - else: + elif characteristic == 0: assert P([1, 1]) // 2 == P([0, 0]) assert P([1, 1]) % 2 == P([1, 1]) + elif nmod_poly_will_crash: + pass + else: + # Z/nZ for n not prime + if characteristic % 2 == 0: + assert raises(lambda: P([1, 1]) // 2, DomainError) + assert raises(lambda: P([1, 1]) % 2, DomainError) + else: + 1/0 assert 1 // P([1, 1]) == P([0]) assert 1 % P([1, 1]) == P([1]) @@ -2694,14 +2728,22 @@ def setbad(obj, i, val): if is_field: assert P([2, 2]) / 2 == P([1, 1]) assert P([1, 2]) / 2 == P([S(1)/2, 1]) - else: + elif characteristic == 0: assert P([2, 2]) / 2 == P([1, 1]) assert raises(lambda: P([1, 2]) / 2, DomainError) + elif nmod_poly_will_crash: + pass + else: + # Z/nZ for n not prime + assert raises(lambda: P([2, 2]) / 2, DomainError) + assert raises(lambda: P([1, 2]) / 2, DomainError) + assert raises(lambda: P([1, 2]) / 0, ZeroDivisionError) - assert P([1, 2, 1]) / P([1, 1]) == P([1, 1]) - assert raises(lambda: 1 / P([1, 1]), DomainError) - assert raises(lambda: P([1, 2, 1]) / P([1, 2]), DomainError) + if not nmod_poly_will_crash: + assert P([1, 2, 1]) / P([1, 1]) == P([1, 1]) + assert raises(lambda: 1 / P([1, 1]), DomainError) + assert raises(lambda: P([1, 2, 1]) / P([1, 2]), DomainError) assert P([1, 1]) ** 0 == P([1]) assert P([1, 1]) ** 1 == P([1, 1]) @@ -2717,8 +2759,15 @@ def setbad(obj, i, val): else: assert p * p % mod == pow(p, 2, mod) - assert P([1, 2, 1]).gcd(P([1, 1])) == P([1, 1]) - assert raises(lambda: P([1, 2, 1]).gcd(None), TypeError) + if not composite_characteristic: + assert P([1, 2, 1]).gcd(P([1, 1])) == P([1, 1]) + assert raises(lambda: P([1, 2, 1]).gcd(None), TypeError) + elif nmod_poly_will_crash: + pass + else: + # Z/nZ for n not prime + assert raises(lambda: P([1, 2, 1]).gcd(P([1, 1])), DomainError) + assert raises(lambda: P([1, 2, 1]).gcd(None), TypeError) if is_field: p1 = P([1, 0, 1]) @@ -2727,10 +2776,20 @@ def setbad(obj, i, val): assert p1.xgcd(p2) == (g, s, t) assert raises(lambda: p1.xgcd(None), TypeError) - assert P([1, 2, 1]).factor() == (S(1), [(P([1, 1]), 2)]) + if not composite_characteristic: + assert P([1, 2, 1]).factor() == (S(1), [(P([1, 1]), 2)]) + elif nmod_poly_will_crash: + pass + else: + assert raises(lambda: P([1, 2, 1]).factor(), DomainError) - assert P([1, 2, 1]).sqrt() == P([1, 1]) - assert raises(lambda: P([1, 2, 2]).sqrt(), ValueError), f"{P}, {P([1, 2, 2]).sqrt()}" + if not composite_characteristic: + assert P([1, 2, 1]).sqrt() == P([1, 1]) + assert raises(lambda: P([1, 2, 2]).sqrt(), DomainError) + elif nmod_poly_will_crash: + pass + else: + assert raises(lambda: P([1, 2, 1]).sqrt(), DomainError) if P == flint.fmpq_poly: assert raises(lambda: P([1, 2, 1], 3).sqrt(), ValueError) @@ -2748,45 +2807,51 @@ def setbad(obj, i, val): if type(p) == flint.fq_default_poly: assert raises(lambda: p.integral(), NotImplementedError) + def _all_mpolys(): return [ - (flint.fmpz_mpoly, flint.fmpz_mpoly_ctx.get_context, flint.fmpz, False), - (flint.fmpq_mpoly, flint.fmpq_mpoly_ctx.get_context, flint.fmpq, True), + (flint.fmpz_mpoly, flint.fmpz_mpoly_ctx.get_context, flint.fmpz, False, flint.fmpz(0)), + (flint.fmpq_mpoly, flint.fmpq_mpoly_ctx.get_context, flint.fmpq, True, flint.fmpz(0)), ( flint.fmpz_mod_mpoly, lambda *args, **kwargs: flint.fmpz_mod_mpoly_ctx.get_context(*args, **kwargs, modulus=101), lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(101)), True, + flint.fmpz(101), ), ( flint.fmpz_mod_mpoly, lambda *args, **kwargs: flint.fmpz_mod_mpoly_ctx.get_context(*args, **kwargs, modulus=100), lambda x: flint.fmpz_mod(x, flint.fmpz_mod_ctx(100)), False, + flint.fmpz(100), ), ( flint.nmod_mpoly, lambda *args, **kwargs: flint.nmod_mpoly_ctx.get_context(*args, **kwargs, modulus=101), lambda x: flint.nmod(x, 101), True, + flint.fmpz(101), ), ( flint.nmod_mpoly, lambda *args, **kwargs: flint.nmod_mpoly_ctx.get_context(*args, **kwargs, modulus=100), lambda x: flint.nmod(x, 100), False, + flint.fmpz(100), ), ] def test_mpolys(): - for P, get_context, S, is_field in _all_mpolys(): + for P, get_context, S, is_field, characteristic in _all_mpolys(): - # Division under modulo will raise a flint exception if something is not invertible, crashing the program. We - # can't tell before what is invertible and what is not before hand so we always raise an exception, except for - # fmpz_mpoly, that returns an bool noting if the division is exact or not. - division_not_supported = P is not flint.fmpz_mpoly and not is_field - characteristic_zero = not (P is flint.fmpz_mod_mpoly or P is flint.nmod_mpoly) + # Division under modulo will raise a flint exception if something is + # not invertible, crashing the program. We can't tell before what is + # invertible and what is not before hand so we always raise an + # exception, except for fmpz_mpoly, that returns an bool noting if the + # division is exact or not. + composite_characteristic = characteristic != 0 and not characteristic.is_prime() ctx = get_context(nvars=2) @@ -3051,7 +3116,7 @@ def quick_poly(): assert raises(lambda: quick_poly().imul(P(ctx=ctx1)), IncompatibleContextError) assert raises(lambda: quick_poly().imul(None), NotImplementedError) - if division_not_supported: + if composite_characteristic: assert raises(lambda: quick_poly() // mpoly({(1, 1): 1}), DomainError) assert raises(lambda: quick_poly() % mpoly({(1, 1): 1}), DomainError) assert raises(lambda: divmod(quick_poly(), mpoly({(1, 1): 1})), DomainError) @@ -3081,7 +3146,7 @@ def quick_poly(): f = mpoly({(1, 1): 4, (0, 0): 1}) g = mpoly({(0, 1): 2, (1, 0): 2}) - if not division_not_supported: + if not composite_characteristic: assert 1 // quick_poly() == P(ctx=ctx) assert 1 % quick_poly() == P(1, ctx=ctx) assert divmod(1, quick_poly()) == (P(ctx=ctx), P(1, ctx=ctx)) @@ -3148,7 +3213,7 @@ def quick_poly(): # # XXX: Not sure what this should do in general: assert raises(lambda: pow(P(1, ctx=ctx), 2, 3), NotImplementedError) - if division_not_supported: + if composite_characteristic: assert raises(lambda: (f * g).gcd(f), DomainError) else: if is_field: @@ -3158,24 +3223,24 @@ def quick_poly(): assert raises(lambda: quick_poly().gcd(None), TypeError) assert raises(lambda: quick_poly().gcd(P(ctx=ctx1)), IncompatibleContextError) - if division_not_supported: + if composite_characteristic: # Factorisation not allowed over Z/nZ for n not prime. # Flint would abort so we raise an exception instead: assert raises(lambda: (f * g).factor(), DomainError) - elif characteristic_zero: + elif characteristic == 0: # Primitive factors over Z for fmpz_mpoly and fmpq_mpoly assert (f * g).factor() == (S(2), [(g / 2, 1), (f, 1)]) elif is_field: # Monic polynomials over Z/pZ for nmod_mpoly and fmpz_mod_mpoly assert (f * g).factor() == (S(8), [(g / 2, 1), (f / 4, 1)]) - if division_not_supported: + if composite_characteristic: assert raises(lambda: (f * g).sqrt(), DomainError) else: assert (f * f).sqrt() == f if P is flint.fmpz_mpoly: assert (f * f).sqrt(assume_perfect_square=True) == f - assert raises(lambda: quick_poly().sqrt(), ValueError) + assert raises(lambda: quick_poly().sqrt(), DomainError) p = quick_poly() assert p.derivative(0) == p.derivative("x0") == mpoly({(0, 0): 3, (1, 2): 8}) @@ -3233,6 +3298,239 @@ def test_fmpz_mpoly_vec(): assert raises(lambda: vec.__setitem__(0, ctx1.from_dict({})), IncompatibleContextError) +def _all_polys_mpolys(): + + for P, S, is_field, characteristic in _all_polys(): + x = P([0, 1]) + y = None + assert isinstance(x, ( + flint.fmpz_poly, + flint.fmpq_poly, + flint.nmod_poly, + flint.fmpz_mod_poly, + flint.fq_default_poly, + )) + yield P, S, [x, y], is_field, characteristic + + for P, get_context, S, is_field, characteristic in _all_mpolys(): + ctx = get_context(2, flint.Ordering.lex, nametup=("x", "y")) + x, y = ctx.gens() + assert isinstance(x, ( + flint.fmpz_mpoly, + flint.fmpq_mpoly, + flint.nmod_mpoly, + flint.fmpz_mod_mpoly, + )) + yield P, S, [x, y], is_field, characteristic + + +def test_factor_poly_mpoly(): + """Test that factor() is consistent across different poly/mpoly types.""" + + def check(p, coeff, factors): + # Check all the types + lc = p.leading_coefficient() + assert type(coeff) is type(lc) + assert isinstance(factors, list) + assert all(isinstance(f, tuple) for f in factors) + for fac, m in factors: + assert type(fac) is type(p) + assert type(m) is int + + # Check the actual factorisation! + res = coeff + for fac, m in factors: + res *= fac ** m + assert res == p + + def sort(factors): + def sort_key(p): + fac, m = p + return (m, sorted(str(i) for i in fac.coeffs())) + return sorted(factors, key=sort_key) + + def factor(p): + coeff, factors = p.factor() + check(p, coeff, factors) + return coeff, sort(factors) + + def factor_sqf(p): + coeff, factors = p.factor_squarefree() + check(p, coeff, factors) + return coeff, sort(factors) + + 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 + + try: + S(4).sqrt() ** 2 == S(4) + except DomainError: + pass + assert raises(lambda: (x**2).sqrt(), DomainError) + assert raises(lambda: x.gcd(x), DomainError) + assert raises(lambda: x.gcd(None), TypeError) + assert raises(lambda: x.factor(), DomainError) + assert raises(lambda: x.factor_squarefree(), DomainError) + + # All tests below can be expected to raise DomainError + # Not sure if that is guaranteed in all cases though... + continue + + assert S(0).sqrt() == S(0) + assert S(1).sqrt() == S(1) + assert S(4).sqrt()**2 == S(4) + + for i in range(-100, 100): + try: + assert S(i).sqrt() ** 2 == S(i) + except DomainError: + pass + + if characteristic == 0: + assert raises(lambda: S(-1).sqrt(), DomainError) + + assert (0*x).sqrt() == 0*x + assert (1*x/x).sqrt() == 0*x + 1 + assert (4*x/x).sqrt()**2 == 0*x + 4 + + for i in range(-100, 100): + try: + assert (i*x).sqrt() ** 2 == i*x + except DomainError: + pass + + assert (x**2).sqrt() == x + assert (S(4)*x**2).sqrt()**2 == S(4)*x**2 + assert raises(lambda: (x**2 + 1).sqrt(), DomainError) + + assert factor(0*x) == (S(0), []) + assert factor(0*x + 1) == (S(1), []) + assert factor(0*x + 3) == (S(3), []) + assert factor(x) == (S(1), [(x, 1)]) + assert factor(-x) == (S(-1), [(x, 1)]) + assert factor(x**2) == (S(1), [(x, 2)]) + assert factor(2*(x+1)) == (S(2), [(x+1, 1)]) + + assert factor_sqf(0*x) == (S(0), []) + assert factor_sqf(0*x + 1) == (S(1), []) + assert factor_sqf(0*x + 3) == (S(3), []) + assert factor_sqf(-x) == (S(-1), [(x, 1)]) + assert factor_sqf(x**2) == (S(1), [(x, 2)]) + assert factor_sqf(2*(x+1)) == (S(2), [(x+1, 1)]) + + assert (0*x).gcd(0*x) == 0*x + assert (0*x).gcd(0*x + 1) == S(1) + + if not is_field: + assert (0*x).gcd(0*x + 3) == S(3) + else: + assert (0*x).gcd(0*x + 3) == S(1) + + assert (2*x).gcd(x) == x + assert (2*x).gcd(x**2) == x + assert (2*x).gcd(x**2 + 1) == S(1) + + if not is_field: + # primitive gcd over Z + assert (2*x).gcd(4*x**2) == 2*x + else: + # monic gcd over Q, Z/pZ and GF(p^d) + assert (2*x).gcd(4*x**2) == x + + if is_field and y is None: + # xgcd is defined and consistent for all univariate polynomials + # over a field (Q, Z/pZ, GF(p^d)). + assert (2*x).xgcd(4*x) == (x, P(0), P(1)/4) + assert (2*x).xgcd(4*x**2+1) == (P(1), -2*x, P(1)) + + # mpoly types have a slightly different squarefree factorisation + # because they handle trivial factors differently. It looks like a + # monomial gcd is extracted but not recombined so the square-free + # factors might not have unique multiplicities. + # + # Maybe it is worth making them consistent by absorbing the power + # of x into a factor of equal multiplicity. + assert factor(x*(x+1)) == (S(1), [(x, 1), (x+1, 1)]) + if y is None: + # *_poly types + assert factor_sqf(x*(x+1)) == (S(1), [(x**2+x, 1)]) + else: + # *_mpoly types + assert factor_sqf(x*(x+1)) == (S(1), [(x, 1), (x+1, 1)]) + + # This is the same for all types because the extracted monomial has + # a unique multiplicity. + assert factor_sqf(x**2*(x+1)) == (S(1), [(x+1, 1), (x, 2)]) + + # This is the same for all types because there is no trivial monomial + # factor to extract. + assert factor((x-1)*(x+1)) == (S(1), sort([(x-1, 1), (x+1, 1)])) + assert factor_sqf((x-1)*(x+1)) == (S(1), [(x**2-1, 1)]) + + # Some finite fields have sqrt(-1) so we can factor x**2 + 1 + try: + i = S(-1).sqrt() + except DomainError: + i = None + + p = 3*(x-1)**2*(x+1)**2*(x**2 + 1)**3 + assert factor_sqf(p) == (S(3), [(x**2 - 1, 2), (x**2 + 1, 3)]) + + if i is not None: + assert factor(p) == (S(3), sort([(x+1, 2), (x-1, 2), (x+i, 3), (x-i, 3)])) + else: + assert factor(p) == (S(3), sort([(x-1, 2), (x+1, 2), (x**2+1, 3)])) + + if characteristic == 0: + # primitive factors over Z for Z and Q. + assert factor(2*x+1) == (S(1), [(2*x+1, 1)]) + else: + # monic factors over Z/pZ and GF(p^d) + assert factor(2*x+1) == (S(2), [(x+S(1)/2, 1)]) + + if is_field: + if characteristic == 0: + assert factor((2*x+1)/7) == (S(1)/7, [(2*x+1, 1)]) + else: + assert factor((2*x+1)/7) == (S(2)/7, [(x+S(1)/2, 1)]) + + if y is not None: + + # *_mpoly types + + assert factor(x*y+1) == (S(1), [(x*y+1, 1)]) + assert factor(x*y) == (S(1), [(x, 1), (y, 1)]) + + assert factor_sqf((x*y+1)**2*(x*y-1)) == (S(1), [(x*y-1, 1), (x*y+1, 2)]) + + p = 2*x + y + if characteristic == 0: + assert factor(p) == factor_sqf(p) == (S(1), [(p, 1)]) + else: + assert factor(p) == factor_sqf(p) == (S(2), [(p/2, 1)]) + + if is_field: + p = (2*x + y)/7 + if characteristic == 0: + assert factor(p) == factor_sqf(p) == (S(1)/7, [(7*p, 1)]) + else: + assert factor(p) == factor_sqf(p) == (S(2)/7, [(7*p/2, 1)]) + + if not is_field: + # primitive gcd over Z + assert (2*(x+y)).gcd(4*(x+y)**2) == 2*(x+y) + else: + # monic gcd over Q, Z/pZ and GF(p^d) + assert (2*(x+y)).gcd(4*(x+y)**2) == x + y + + def _all_matrices(): """Return a list of matrix types and scalar types.""" R163 = flint.fmpz_mod_ctx(163) @@ -3941,7 +4239,7 @@ def test_fq_default(): nqr = gf.random_element() if not nqr.is_square(): break - assert raises(lambda: nqr.sqrt(), ValueError) + assert raises(lambda: nqr.sqrt(), DomainError) def test_fq_default_poly(): @@ -4152,6 +4450,8 @@ def test_all_tests(): test_division_poly, test_division_matrix, + test_factor_poly_mpoly, + test_polys, test_mpolys, diff --git a/src/flint/types/fmpq.pyx b/src/flint/types/fmpq.pyx index fd9399b0..28a9dc67 100644 --- a/src/flint/types/fmpq.pyx +++ b/src/flint/types/fmpq.pyx @@ -486,3 +486,17 @@ cdef class fmpq(flint_scalar): return v else: raise OverflowError("fmpq_pow_fmpz(): exponent too large") + + def sqrt(self): + """ + Return exact rational square root of self or raise an error. + + >>> fmpq(9, 4).sqrt() + 3/2 + >>> fmpq(8).sqrt() + Traceback (most recent call last): + ... + flint.utils.flint_exceptions.DomainError: not a square number + + """ + return fmpq(self.numer().sqrt(), self.denom().sqrt()) diff --git a/src/flint/types/fmpq_mpoly.pyx b/src/flint/types/fmpq_mpoly.pyx index 88219b54..0165f9ac 100644 --- a/src/flint/types/fmpq_mpoly.pyx +++ b/src/flint/types/fmpq_mpoly.pyx @@ -858,6 +858,25 @@ cdef class fmpq_mpoly(flint_mpoly): fmpq_mpoly_total_degree_fmpz(( res).val, self.val, self.ctx.val) return res + def leading_coefficient(self): + """ + Leading coefficient in the monomial ordering. + + >>> from flint import Ordering + >>> ctx = fmpq_mpoly_ctx(2, Ordering.lex, ['x', 'y']) + >>> x, y = ctx.gens() + >>> p = 2*x*y + 3*x + 4*y**2 + 5 + >>> p + 2*x*y + 3*x + 4*y^2 + 5 + >>> p.leading_coefficient() + 2 + + """ + if fmpq_mpoly_is_zero(self.val, self.ctx.val): + return fmpq(0) + else: + return self.coefficient(0) + def repr(self): return f"{self.ctx}.from_dict({self.to_dict()})" @@ -906,7 +925,7 @@ cdef class fmpq_mpoly(flint_mpoly): if fmpq_mpoly_sqrt(res.val, self.val, self.ctx.val): return res else: - raise ValueError("polynomial is not a perfect square") + raise DomainError("polynomial is not a perfect square") def factor(self): """ @@ -940,7 +959,7 @@ cdef class fmpq_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_init_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) c = fmpq.__new__(fmpq) fmpq_set((c).val, fac.constant) @@ -979,7 +998,7 @@ cdef class fmpq_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_init_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) c = fmpq.__new__(fmpq) fmpq_set((c).val, fac.constant) diff --git a/src/flint/types/fmpq_poly.pyx b/src/flint/types/fmpq_poly.pyx index f83f471b..0a00fbe5 100644 --- a/src/flint/types/fmpq_poly.pyx +++ b/src/flint/types/fmpq_poly.pyx @@ -176,6 +176,24 @@ cdef class fmpq_poly(flint_poly): def is_one(self): return fmpq_poly_is_one(self.val) + def leading_coefficient(self): + """ + Returns the leading coefficient of the polynomial. + + >>> f = fmpq_poly([1, 2, 3]) + >>> f + 3*x^2 + 2*x + 1 + >>> f.leading_coefficient() + 3 + """ + cdef fmpq x + cdef slong d + d = fmpq_poly_degree(self.val) + x = fmpq.__new__(fmpq) + if d >= 0: + fmpq_poly_get_coeff_fmpq(x.val, self.val, d) + return x + def __call__(self, other): t = any_as_fmpz(other) if t is not NotImplemented: @@ -393,28 +411,64 @@ cdef class fmpq_poly(flint_poly): fmpq_poly_xgcd(res1.val, res2.val, res3.val, self.val, (other).val) return (res1, res2, res3) - def factor(self): + def factor(self, *, monic=False): """ Factors *self* into irreducible polynomials. Returns (*c*, *factors*) where *c* is the leading coefficient and *factors* is a list of - (*poly*, *exp*) pairs with all *poly* monic. + (*poly*, *exp*). >>> fmpq_poly.legendre_p(5).factor() - (63/8, [(x, 1), (x^4 + (-10/9)*x^2 + 5/21, 1)]) + (1/8, [(x, 1), (63*x^4 + (-70)*x^2 + 15, 1)]) >>> (fmpq_poly([1,-1],10) ** 5 * fmpq_poly([1,2,3],7)).factor() + (-1/700000, [(3*x^2 + 2*x + 1, 1), (x + (-1), 5)]) + + Since python-flint 0.7.0 this returns primitive denominator-free + factors consistent with ``fmpq_mpoly.factor()``. In previous versions + of python-flint all factors were made monic. Pass ``monic=True`` to get + monic factors instead. + + >>> fmpq_poly.legendre_p(5).factor(monic=True) + (63/8, [(x, 1), (x^4 + (-10/9)*x^2 + 5/21, 1)]) + >>> (fmpq_poly([1,-1],10) ** 5 * fmpq_poly([1,2,3],7)).factor(monic=True) (-3/700000, [(x^2 + 2/3*x + 1/3, 1), (x + (-1), 5)]) """ c, fac = self.numer().factor() c = fmpq(c) - for i in range(len(fac)): - base, exp = fac[i] - lead = base[base.degree()] - base = fmpq_poly(base, lead) - c *= lead ** exp - fac[i] = (base, exp) + + if monic: + for i in range(len(fac)): + base, exp = fac[i] + lead = base[base.degree()] + base = fmpq_poly(base, lead) + c *= lead ** exp + fac[i] = (base, exp) + else: + fac = [(fmpq_poly(f), m) for f, m in fac] + return c / self.denom(), fac + def factor_squarefree(self): + """ + Factors *self* into square-free polynomials. Returns (*c*, *factors*) + where *c* is the leading coefficient and *factors* is a list of + (*poly*, *exp*). + + >>> x = fmpq_poly([0, 1]) + >>> p = x**2 * (x/2 - 1)**2 * (x + 1)**3 + >>> p + 1/4*x^7 + (-1/4)*x^6 + (-5/4)*x^5 + 1/4*x^4 + 2*x^3 + x^2 + >>> p.factor_squarefree() + (1/4, [(x^2 + (-2)*x, 2), (x + 1, 3)]) + >>> p.factor() + (1/4, [(x, 2), (x + (-2), 2), (x + 1, 3)]) + + """ + c, fac = self.numer().factor_squarefree() + c = fmpq(c) / self.denom() + fac = [(fmpq_poly(f), m) for f, m in fac] + return c, fac + def sqrt(self): """ Return the exact square root of this polynomial or ``None``. diff --git a/src/flint/types/fmpz.pyx b/src/flint/types/fmpz.pyx index 0b163750..3c4c75e8 100644 --- a/src/flint/types/fmpz.pyx +++ b/src/flint/types/fmpz.pyx @@ -883,29 +883,102 @@ cdef class fmpz(flint_scalar): return self.bit_length() def isqrt(self): + """ + Return square root rounded down. + + >>> fmpz(9).isqrt() + 3 + >>> fmpz(8).isqrt() + 2 + + """ + cdef fmpz v + + if fmpz_sgn(self.val) < 0: + raise DomainError("integer square root of a negative number") + + v = fmpz() + fmpz_sqrt(v.val, self.val) + return v + + def sqrt(self): + """ + Return exact integer square root of self or raise an error. + + >>> fmpz(9).sqrt() + 3 + >>> fmpz(8).sqrt() + Traceback (most recent call last): + ... + flint.utils.flint_exceptions.DomainError: not a square number + + """ cdef fmpz v + if fmpz_sgn(self.val) < 0: - raise ValueError("integer square root of a negative number") + raise DomainError("integer square root of a negative number") + v = fmpz() fmpz_sqrt(v.val, self.val) + + c = fmpz() + fmpz_mul(c.val, v.val, v.val) + if not fmpz_equal(c.val, self.val): + raise DomainError("not a square number") + return v def sqrtrem(self): + """ + Return the integer square root of self and remainder. + + >>> fmpz(9).sqrtrem() + (3, 0) + >>> fmpz(8).sqrtrem() + (2, 4) + >>> c = fmpz(123456789012345678901234567890) + >>> u, v = c.sqrtrem() + >>> u ** 2 + v == c + True + + """ cdef fmpz u, v + if fmpz_sgn(self.val) < 0: - raise ValueError("integer square root of a negative number") + raise DomainError("integer square root of a negative number") + u = fmpz() v = fmpz() fmpz_sqrtrem(u.val, v.val, self.val) + return u, v # warning: m should be prime! - def sqrtmod(self, m): + def sqrtmod(self, p): + """ + Return modular square root of self modulo *p* or raise an error. + + >>> fmpz(10).sqrtmod(13) + 6 + >>> (6**2) % 13 + 10 + >>> fmpz(11).sqrtmod(13) + Traceback (most recent call last): + ... + flint.utils.flint_exceptions.DomainError: modular square root does not exist + + The modulus *p* must be a prime number. + """ cdef fmpz v + v = fmpz() - m = fmpz(m) - if not fmpz_sqrtmod(v.val, self.val, (m).val): - raise ValueError("unable to compute modular square root") + if fmpz_is_zero(self.val): + return v + + p = fmpz(p) + if not fmpz_sqrtmod(v.val, self.val, (p).val): + raise DomainError("modular square root does not exist") + return v def root(self, long n): diff --git a/src/flint/types/fmpz_mod.pyx b/src/flint/types/fmpz_mod.pyx index 63c4c1e3..a29f290e 100644 --- a/src/flint/types/fmpz_mod.pyx +++ b/src/flint/types/fmpz_mod.pyx @@ -10,6 +10,7 @@ from flint.flintlib.fmpz cimport( fmpz_is_probabprime, fmpz_mul, fmpz_invmod, + fmpz_sqrtmod, fmpz_divexact, fmpz_gcd, fmpz_is_one, @@ -29,6 +30,9 @@ from flint.types.fmpz cimport( cimport cython cimport libc.stdlib +from flint.utils.flint_exceptions import DomainError + + cdef class fmpz_mod_ctx: r""" Context object for creating :class:`~.fmpz_mod` initalised @@ -578,3 +582,34 @@ cdef class fmpz_mod(flint_scalar): ) return res + + def sqrt(self): + """ + Return the square root of this ``fmpz_mod`` or raise an exception. + + >>> ctx = fmpz_mod_ctx(13) + >>> s = ctx(10).sqrt() + >>> s + fmpz_mod(6, 13) + >>> s * s + fmpz_mod(10, 13) + >>> ctx(11).sqrt() + Traceback (most recent call last): + ... + flint.utils.flint_exceptions.DomainError: no square root exists for 11 mod 13 + + The modulus must be prime. + + """ + cdef fmpz_mod v + + v = fmpz_mod.__new__(fmpz_mod) + v.ctx = self.ctx + + if fmpz_is_zero(self.val): + return v + + if not fmpz_sqrtmod(v.val, self.val, self.ctx.val.n): + raise DomainError("no square root exists for {} mod {}".format(self, self.ctx.modulus())) + + return v diff --git a/src/flint/types/fmpz_mod_mpoly.pyx b/src/flint/types/fmpz_mod_mpoly.pyx index 43f5b328..cf61d541 100644 --- a/src/flint/types/fmpz_mod_mpoly.pyx +++ b/src/flint/types/fmpz_mod_mpoly.pyx @@ -757,6 +757,25 @@ cdef class fmpz_mod_mpoly(flint_mpoly): fmpz_mod_mpoly_total_degree_fmpz(( res).val, self.val, self.ctx.val) return res + def leading_coefficient(self): + """ + Leading coefficient in the monomial ordering. + + >>> from flint import Ordering + >>> ctx = fmpz_mod_mpoly_ctx(2, Ordering.lex, ['x', 'y'], 11) + >>> x, y = ctx.gens() + >>> p = 2*x*y + 3*x + 4*y**2 + 5 + >>> p + 2*x*y + 3*x + 4*y^2 + 5 + >>> p.leading_coefficient() + 2 + + """ + if fmpz_mod_mpoly_is_zero(self.val, self.ctx.val): + return fmpz(0) + else: + return self.coefficient(0) + def repr(self): return f"{self.ctx}.from_dict({self.to_dict()})" @@ -779,12 +798,12 @@ cdef class fmpz_mod_mpoly(flint_mpoly): 4*x0*x1 + 1 """ cdef fmpz_mod_mpoly res - if not self.ctx.is_prime(): - raise DomainError("gcd with non-prime modulus is not supported") - elif not typecheck(other, fmpz_mod_mpoly): + if not typecheck(other, fmpz_mod_mpoly): raise TypeError("argument must be a fmpz_mod_mpoly") elif (self).ctx is not (other).ctx: raise IncompatibleContextError(f"{(self).ctx} is not {(other).ctx}") + elif not self.ctx.is_prime(): + raise DomainError("gcd with non-prime modulus is not supported") res = create_fmpz_mod_mpoly(self.ctx) fmpz_mod_mpoly_gcd(res.val, (self).val, (other).val, res.ctx.val) return res @@ -808,7 +827,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): if fmpz_mod_mpoly_sqrt(res.val, self.val, self.ctx.val): return res else: - raise ValueError("polynomial is not a perfect square") + raise DomainError("polynomial is not a perfect square") def factor(self): """ @@ -846,7 +865,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) c = fmpz.__new__(fmpz) fmpz_set((c).val, fac.constant) @@ -890,7 +909,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) c = fmpz.__new__(fmpz) fmpz_set((c).val, fac.constant) diff --git a/src/flint/types/fmpz_mod_poly.pyx b/src/flint/types/fmpz_mod_poly.pyx index 328600de..ce9cba76 100644 --- a/src/flint/types/fmpz_mod_poly.pyx +++ b/src/flint/types/fmpz_mod_poly.pyx @@ -439,9 +439,8 @@ cdef class fmpz_mod_poly(flint_poly): if other == 0: raise ZeroDivisionError(f"Cannot divide by zero") - - if not other.is_unit(): - raise ZeroDivisionError(f"Cannot divide by {other} modulo {self.ctx.modulus()}") + elif not other.is_unit(): + raise DomainError(f"Cannot divide by {other} modulo {self.ctx.modulus()}") res = self.ctx.new_ctype_poly() fmpz_mod_poly_scalar_div_fmpz( @@ -520,8 +519,12 @@ cdef class fmpz_mod_poly(flint_poly): if left is NotImplemented: return NotImplemented - if not right.leading_coefficient().is_unit(): - raise ZeroDivisionError(f"The leading term of {right} must be a unit modulo N") + lc = right.leading_coefficient() + + if lc.is_zero(): + raise ZeroDivisionError(f"Cannot divide by zero") + elif not lc.is_unit(): + raise DomainError(f"The leading term of {right} must be a unit modulo N") res = (left).ctx.new_ctype_poly() fmpz_mod_poly_div( @@ -648,7 +651,7 @@ cdef class fmpz_mod_poly(flint_poly): ) if not fmpz_is_one(f): fmpz_clear(f) - raise ValueError( + raise DomainError( f"Cannot compute remainder of {left} modulo {right}" ) @@ -1266,13 +1269,13 @@ cdef class fmpz_mod_poly(flint_poly): """ cdef fmpz_mod_poly res - if not self.ctx.is_prime(): - raise NotImplementedError("gcd algorithm assumes that the modulus is prime") - other = self.ctx.any_as_fmpz_mod_poly(other) if other is NotImplemented: raise TypeError(f"Cannot interpret {other} as a polynomial") + if not self.ctx.is_prime(): + raise DomainError("gcd algorithm assumes that the modulus is prime") + res = self.ctx.new_ctype_poly() fmpz_mod_poly_gcd( res.val, self.val, (other).val, self.ctx.mod.val @@ -1485,12 +1488,15 @@ cdef class fmpz_mod_poly(flint_poly): cdef fmpz_mod_poly res cdef int check + if not self.ctx.is_prime(): + raise DomainError("sqrt algorithm assumes that the modulus is prime") + res = self.ctx.new_ctype_poly() check = fmpz_mod_poly_sqrt( res.val, self.val, res.ctx.mod.val ) if check != 1: - raise ValueError( + raise DomainError( f"Cannot compute square-root {self}" ) return res @@ -1762,7 +1768,7 @@ cdef class fmpz_mod_poly(flint_poly): cdef int i if not self.ctx.is_prime(): - raise NotImplementedError("factor_squarefree algorithm assumes that the modulus is prime") + raise DomainError("factor_squarefree algorithm assumes that the modulus is prime") fmpz_mod_poly_factor_init(fac, self.ctx.mod.val) fmpz_mod_poly_factor_squarefree(fac, self.val, self.ctx.mod.val) @@ -1796,7 +1802,21 @@ cdef class fmpz_mod_poly(flint_poly): cdef int i if not self.ctx.is_prime(): - raise NotImplementedError("factor algorithm assumes that the modulus is prime") + raise DomainError("factor algorithm assumes that the modulus is prime") + + # XXX: fmpz_mod_poly_factor with modulus 163 crashes on the zero poly: + # + # Exception (fmpz_mod_poly_powmod_fmpz_binexp). Divide by zero + # + # We handle this special case first: + cdef fmpz_mod constant + if self.is_constant(): + if self.is_zero(): + constant = fmpz_mod.__new__(fmpz_mod) + constant.ctx = self.ctx.mod + else: + constant = self[0] + return (constant, []) fmpz_mod_poly_factor_init(fac, self.ctx.mod.val) if algorithm == None: diff --git a/src/flint/types/fmpz_mpoly.pyx b/src/flint/types/fmpz_mpoly.pyx index b964050d..fa336d1d 100644 --- a/src/flint/types/fmpz_mpoly.pyx +++ b/src/flint/types/fmpz_mpoly.pyx @@ -836,6 +836,25 @@ cdef class fmpz_mpoly(flint_mpoly): fmpz_mpoly_total_degree_fmpz(( res).val, self.val, self.ctx.val) return res + def leading_coefficient(self): + """ + Leading coefficient in the monomial ordering. + + >>> from flint import Ordering + >>> ctx = fmpz_mpoly_ctx(2, Ordering.lex, ['x', 'y']) + >>> x, y = ctx.gens() + >>> p = 2*x*y + 3*x + 4*y**2 + 5 + >>> p + 2*x*y + 3*x + 4*y^2 + 5 + >>> p.leading_coefficient() + 2 + + """ + if fmpz_mpoly_is_zero(self.val, self.ctx.val): + return fmpz(0) + else: + return self.coefficient(0) + def repr(self): return f"{self.ctx}.from_dict({self.to_dict()})" @@ -884,7 +903,7 @@ cdef class fmpz_mpoly(flint_mpoly): if fmpz_mpoly_sqrt_heap(res.val, self.val, self.ctx.val, not assume_perfect_square): return res else: - raise ValueError("polynomial is not a perfect square") + raise DomainError("polynomial is not a perfect square") def factor(self): """ @@ -919,7 +938,7 @@ cdef class fmpz_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) c = fmpz.__new__(fmpz) fmpz_set((c).val, fac.constant) @@ -928,7 +947,7 @@ cdef class fmpz_mpoly(flint_mpoly): def factor_squarefree(self): """ - Factors self into irreducible factors, returning a tuple + Factors self into square-free factors, returning a tuple (c, factors) where c is the content of the coefficients and factors is a list of (poly, exp) pairs. @@ -959,7 +978,7 @@ cdef class fmpz_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) c = fmpz.__new__(fmpz) fmpz_set((c).val, fac.constant) diff --git a/src/flint/types/fmpz_poly.pyx b/src/flint/types/fmpz_poly.pyx index 6d72a5d3..a32d3a92 100644 --- a/src/flint/types/fmpz_poly.pyx +++ b/src/flint/types/fmpz_poly.pyx @@ -144,6 +144,24 @@ cdef class fmpz_poly(flint_poly): def is_one(self): return fmpz_poly_is_one(self.val) + def leading_coefficient(self): + """ + Returns the leading coefficient of the polynomial. + + >>> f = fmpz_poly([1, 2, 3]) + >>> f + 3*x^2 + 2*x + 1 + >>> f.leading_coefficient() + 3 + """ + cdef fmpz x + cdef slong d + d = fmpz_poly_degree(self.val) + x = fmpz.__new__(fmpz) + if d >= 0: + fmpz_poly_get_coeff_fmpz(x.val, self.val, d) + return x + def __call__(self, other): t = any_as_fmpz(other) if t is not NotImplemented: @@ -366,11 +384,36 @@ cdef class fmpz_poly(flint_poly): (1, [(6*x^5 + 5*x^4 + 4*x^3 + 3*x^2 + 2*x + 1, 1)]) """ + return self._factor('irreducible') + + def factor_squarefree(self): + """ + Factors self into square-free factors, returning a tuple + (c, factors) where c is the content of the coefficients and + factors is a list of (poly, exp) pairs. + + >>> x = fmpz_poly([0, 1]) + >>> p = (-3 * x**2 * (x + 1)**2 * (x - 1)**3) + >>> p.factor_squarefree() + (-3, [(x^2 + x, 2), (x + (-1), 3)]) + >>> p.factor() + (-3, [(x, 2), (x + 1, 2), (x + (-1), 3)]) + + """ + return self._factor('squarefree') + + def _factor(self, factor_type): cdef fmpz_poly_factor_t fac cdef int i fmpz_poly_factor_init(fac) - # should use fmpz_poly_factor, but not available in 2.5.2 - fmpz_poly_factor(fac, self.val) + + if factor_type == 'squarefree': + fmpz_poly_factor_squarefree(fac, self.val) + elif factor_type == 'irreducible': + fmpz_poly_factor(fac, self.val) + else: + assert False + res = [0] * fac.num for 0 <= i < fac.num: u = fmpz_poly.__new__(fmpz_poly) @@ -380,6 +423,7 @@ cdef class fmpz_poly(flint_poly): c = fmpz.__new__(fmpz) fmpz_set((c).val, &fac.c) fmpz_poly_factor_clear(fac) + return c, res def complex_roots(self, bint verbose=False): @@ -547,7 +591,7 @@ cdef class fmpz_poly(flint_poly): if fmpz_poly_sqrt(v.val, self.val): return v else: - raise ValueError(f"Cannot compute square root of {self}") + raise DomainError(f"Cannot compute square root of {self}") def deflation(self): cdef fmpz_poly v diff --git a/src/flint/types/fq_default.pyx b/src/flint/types/fq_default.pyx index 0f92fe30..b2593961 100644 --- a/src/flint/types/fq_default.pyx +++ b/src/flint/types/fq_default.pyx @@ -7,6 +7,8 @@ from flint.types.fmpz_mod_poly cimport fmpz_mod_poly, fmpz_mod_poly_ctx from flint.types.nmod_poly cimport nmod_poly from flint.utils.typecheck cimport typecheck +from flint.utils.flint_exceptions import DomainError + # Allow the type to be denoted by strings or integers FQ_TYPES = { "FQ_ZECH" : 1, @@ -750,7 +752,7 @@ cdef class fq_default(flint_scalar): check = fq_default_sqrt(res.val, self.val, self.ctx.val) if check: return res - raise ValueError("element is not a square") + raise DomainError("element is not a square") def is_square(self): """ diff --git a/src/flint/types/fq_default_poly.pyx b/src/flint/types/fq_default_poly.pyx index 1ee8aabf..16fda36d 100644 --- a/src/flint/types/fq_default_poly.pyx +++ b/src/flint/types/fq_default_poly.pyx @@ -982,7 +982,7 @@ cdef class fq_default_poly(flint_poly): res.val, self.val, res.ctx.field.val ) if check != 1: - raise ValueError( + raise DomainError( f"Cannot compute square-root {self}" ) return res diff --git a/src/flint/types/nmod.pyx b/src/flint/types/nmod.pyx index 7bcab98a..20cd03a9 100644 --- a/src/flint/types/nmod.pyx +++ b/src/flint/types/nmod.pyx @@ -12,7 +12,9 @@ from flint.flintlib.nmod_vec cimport * from flint.flintlib.fmpz cimport fmpz_fdiv_ui, fmpz_init, fmpz_clear from flint.flintlib.fmpz cimport fmpz_set_ui, fmpz_get_ui from flint.flintlib.fmpq cimport fmpq_mod_fmpz -from flint.flintlib.ulong_extras cimport n_gcdinv +from flint.flintlib.ulong_extras cimport n_gcdinv, n_sqrtmod + +from flint.utils.flint_exceptions import DomainError cdef int any_as_nmod(mp_limb_t * val, obj, nmod_t mod) except -1: cdef int success @@ -240,3 +242,35 @@ cdef class nmod(flint_scalar): r.mod = self.mod r.val = nmod_pow_fmpz(rval, (e).val, self.mod) return r + + def sqrt(self): + """ + Return the square root of this nmod or raise an exception. + + >>> s = nmod(10, 13).sqrt() + >>> s + 6 + >>> s * s + 10 + >>> nmod(11, 13).sqrt() + Traceback (most recent call last): + ... + flint.utils.flint_exceptions.DomainError: no square root exists for 11 mod 13 + + The modulus must be prime. + + """ + cdef nmod r + cdef mp_limb_t val + r = nmod.__new__(nmod) + r.mod = self.mod + + if self.val == 0: + return r + + val = n_sqrtmod(self.val, self.mod.n) + if val == 0: + raise DomainError("no square root exists for %s mod %s" % (self.val, self.mod.n)) + + r.val = val + return r diff --git a/src/flint/types/nmod_mpoly.pyx b/src/flint/types/nmod_mpoly.pyx index 22fcacc4..02b5b13d 100644 --- a/src/flint/types/nmod_mpoly.pyx +++ b/src/flint/types/nmod_mpoly.pyx @@ -733,6 +733,25 @@ cdef class nmod_mpoly(flint_mpoly): nmod_mpoly_total_degree_fmpz(( res).val, self.val, self.ctx.val) return res + def leading_coefficient(self): + """ + Leading coefficient in the monomial ordering. + + >>> from flint import Ordering + >>> ctx = nmod_mpoly_ctx(2, Ordering.lex, ['x', 'y'], 11) + >>> x, y = ctx.gens() + >>> p = 2*x*y + 3*x + 4*y**2 + 5 + >>> p + 2*x*y + 3*x + 4*y^2 + 5 + >>> p.leading_coefficient() + 2 + + """ + if nmod_mpoly_is_zero(self.val, self.ctx.val): + return nmod(0, self.ctx.modulus()) + else: + return nmod(self.coefficient(0), self.ctx.modulus()) + def repr(self): return f"{self.ctx}.from_dict({self.to_dict()})" @@ -755,12 +774,12 @@ cdef class nmod_mpoly(flint_mpoly): 4*x0*x1 + 1 """ cdef nmod_mpoly res - if not self.ctx.is_prime(): - raise DomainError("gcd with non-prime modulus is not supported") - elif not typecheck(other, nmod_mpoly): + if not typecheck(other, nmod_mpoly): raise TypeError("argument must be a nmod_mpoly") elif (self).ctx is not (other).ctx: raise IncompatibleContextError(f"{(self).ctx} is not {(other).ctx}") + elif not self.ctx.is_prime(): + raise DomainError("gcd with non-prime modulus is not supported") res = create_nmod_mpoly(self.ctx) nmod_mpoly_gcd(res.val, (self).val, (other).val, res.ctx.val) return res @@ -784,7 +803,7 @@ cdef class nmod_mpoly(flint_mpoly): if nmod_mpoly_sqrt(res.val, self.val, self.ctx.val): return res else: - raise ValueError("polynomial is not a perfect square") + raise DomainError("polynomial is not a perfect square") def factor(self): """ @@ -822,9 +841,9 @@ cdef class nmod_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) - constant = fac.constant + constant = nmod(fac.constant, self.ctx.modulus()) nmod_mpoly_factor_clear(fac, self.ctx.val) return constant, res @@ -865,9 +884,9 @@ cdef class nmod_mpoly(flint_mpoly): c = fmpz.__new__(fmpz) fmpz_set((c).val, &fac.exp[i]) - res[i] = (u, c) + res[i] = (u, int(c)) - constant = fac.constant + constant = nmod(fac.constant, self.ctx.modulus()) nmod_mpoly_factor_clear(fac, self.ctx.val) return constant, res diff --git a/src/flint/types/nmod_poly.pyx b/src/flint/types/nmod_poly.pyx index 65f11666..aef9dfb0 100644 --- a/src/flint/types/nmod_poly.pyx +++ b/src/flint/types/nmod_poly.pyx @@ -235,10 +235,21 @@ cdef class nmod_poly(flint_poly): >>> f.leading_coefficient() 133 """ + cdef ulong cu + cdef slong d + cdef nmod c + d = self.degree() if d < 0: - return 0 - return nmod_poly_get_coeff_ui(self.val, d) + cu = 0 + else: + cu = nmod_poly_get_coeff_ui(self.val, d) + + c = nmod.__new__(nmod) + c.mod = self.val.mod + c.val = cu + + return c def inverse_series_trunc(self, slong n): """ @@ -621,16 +632,49 @@ cdef class nmod_poly(flint_poly): (3, [(x + 4, 1), (x + 2, 1), (x^2 + 4*x + 1, 1)]) """ + if algorithm is None: + algorithm = 'irreducible' + elif algorithm not in ('berlekamp', 'cantor-zassenhaus'): + raise ValueError(f"unknown factorization algorithm: {algorithm}") + return self._factor(algorithm) + + def factor_squarefree(self): + """ + Factors *self* into square-free polynomials. Returns (*c*, *factors*) + where *c* is the leading coefficient and *factors* is a list of + (*poly*, *exp*). + + >>> x = nmod_poly([0, 1], 7) + >>> p = x**2 * (x/2 - 1)**2 * (x + 1)**3 + >>> p + 2*x^7 + 5*x^6 + 4*x^5 + 2*x^4 + 2*x^3 + x^2 + >>> p.factor_squarefree() + (2, [(x^2 + 5*x, 2), (x + 1, 3)]) + >>> p.factor() + (2, [(x, 2), (x + 5, 2), (x + 1, 3)]) + + """ + return self._factor('squarefree') + + def _factor(self, factor_type): cdef nmod_poly_factor_t fac cdef mp_limb_t lead cdef int i + nmod_poly_factor_init(fac) - if algorithm == 'berlekamp': + + if factor_type == 'berlekamp': lead = nmod_poly_factor_with_berlekamp(fac, self.val) - elif algorithm == 'cantor-zassenhaus': + elif factor_type == 'cantor-zassenhaus': lead = nmod_poly_factor_with_cantor_zassenhaus(fac, self.val) - else: + elif factor_type == 'irreducible': lead = nmod_poly_factor(fac, self.val) + elif factor_type == 'squarefree': + nmod_poly_factor_squarefree(fac, self.val) + lead = (self.leading_coefficient()).val + else: + assert False + res = [None] * fac.num for 0 <= i < fac.num: u = nmod_poly.__new__(nmod_poly) @@ -639,10 +683,13 @@ cdef class nmod_poly(flint_poly): nmod_poly_set((u).val, &fac.p[i]) exp = fac.exp[i] res[i] = (u, exp) + c = nmod.__new__(nmod) (c).mod = self.val.mod (c).val = lead + nmod_poly_factor_clear(fac) + return c, res def sqrt(nmod_poly self): @@ -653,7 +700,7 @@ cdef class nmod_poly(flint_poly): if nmod_poly_sqrt(res.val, self.val): return res else: - raise ValueError(f"Cannot compute square root of {self}") + raise DomainError(f"Cannot compute square root of {self}") def deflation(self): cdef nmod_poly v