Skip to content

Commit 20f5eba

Browse files
committed
Make fmpq_poly.factor() return primitive factors
1 parent ba1a7fe commit 20f5eba

6 files changed

Lines changed: 123 additions & 15 deletions

File tree

src/flint/flint_base/flint_base.pyx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ from flint.flintlib.flint cimport (
44
__FLINT_RELEASE as _FLINT_RELEASE,
55
slong
66
)
7+
from flint.utils.flint_exceptions import DomainError
78
from flint.flintlib.mpoly cimport ordering_t
89
from flint.flint_base.flint_context cimport thectx
910
from flint.flint_base.flint_base cimport Ordering
@@ -246,9 +247,13 @@ cdef class flint_poly(flint_elem):
246247
roots = []
247248
factors = self.factor()
248249
for fac, m in factors[1]:
249-
if fac.degree() == fac[1] == 1:
250-
v = - fac[0]
251-
roots.append((v, m))
250+
if fac.degree() == 1:
251+
try:
252+
v = - fac[0] / fac[1]
253+
except DomainError:
254+
pass
255+
else:
256+
roots.append((v, m))
252257
return roots
253258

254259
def complex_roots(self):

src/flint/test/test_all.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,6 +3162,85 @@ def test_fmpz_mpoly_vec():
31623162
assert raises(lambda: vec.__setitem__(0, ctx1.from_dict({})), IncompatibleContextError)
31633163

31643164

3165+
def _all_polys_mpolys():
3166+
3167+
for P, S, is_field in _all_polys():
3168+
x = P([0, 1])
3169+
y = None
3170+
assert isinstance(x, (
3171+
flint.fmpz_poly,
3172+
flint.fmpq_poly,
3173+
flint.nmod_poly,
3174+
flint.fmpz_mod_poly,
3175+
flint.fq_default_poly,
3176+
))
3177+
characteristic_zero = isinstance(x, (flint.fmpz_poly, flint.fmpq_poly))
3178+
yield P, S, [x, y], is_field, characteristic_zero
3179+
3180+
for P, ctx_type, S, is_field in _all_mpolys():
3181+
ctx = ctx_type(2, flint.Ordering.lex, ["x", "y"])
3182+
x, y = ctx.gens()
3183+
assert isinstance(x, (
3184+
flint.fmpz_mpoly,
3185+
flint.fmpq_mpoly,
3186+
))
3187+
characteristic_zero = isinstance(x, (flint.fmpz_mpoly, flint.fmpq_mpoly))
3188+
yield P, S, [x, y], is_field, characteristic_zero
3189+
3190+
3191+
def test_factor_poly_mpoly():
3192+
"""Test that factor() is consistent across different poly/mpoly types."""
3193+
3194+
def factor(p):
3195+
coeff, factors = p.factor()
3196+
try:
3197+
lc = p.leading_coefficient()
3198+
except AttributeError:
3199+
# XXX: Not all univariate types have a leading_coefficient method.
3200+
lc = p[0]
3201+
assert type(coeff) is type(lc)
3202+
assert isinstance(factors, list)
3203+
assert all(isinstance(f, tuple) for f in factors)
3204+
for fac, m in factors:
3205+
assert type(fac) is type(p)
3206+
assert type(m) is int
3207+
return coeff, sorted(factors, key=lambda p: (p[1], str(p[0])))
3208+
3209+
for P, S, [x, y], is_field, characteristic_zero in _all_polys_mpolys():
3210+
3211+
assert factor(x) == (S(1), [(x, 1)])
3212+
assert factor(x**2) == (S(1), [(x, 2)])
3213+
assert factor(x*(x+1)) == (S(1), [(x, 1), (x+1, 1)])
3214+
assert factor(2*(x+1)) == (S(2), [(x+1, 1)])
3215+
3216+
if characteristic_zero:
3217+
# primitive factors over Z for Z and Q.
3218+
assert factor(2*x+1) == (S(1), [(2*x+1, 1)])
3219+
else:
3220+
# monic factors over Z/pZ and GF(p^d)
3221+
assert factor(2*x+1) == (S(2), [(x+S(1)/2, 1)])
3222+
3223+
if is_field:
3224+
if characteristic_zero:
3225+
# primitive factors over Z for Z and Q.
3226+
assert factor((2*x+1)/7) == (S(1)/7, [(2*x+1, 1)])
3227+
else:
3228+
# monic factors over Z/pZ and GF(p^d)
3229+
assert factor((2*x+1)/7) == (S(2)/7, [(x+S(1)/2, 1)])
3230+
3231+
if y is not None:
3232+
3233+
assert factor(x*y+1) == (S(1), [(x*y+1, 1)])
3234+
assert factor(x*y) == (S(1), [(x, 1), (y, 1)])
3235+
3236+
if characteristic_zero:
3237+
# primitive factors over Z for Z and Q.
3238+
assert factor(2*x + y) == (S(1), [(2*x + y, 1)])
3239+
else:
3240+
# monic factors over Z/pZ and GF(p^d)
3241+
assert factor(2*x + y) == (S(1)/2, [(x + y/2, 1)])
3242+
3243+
31653244
def _all_matrices():
31663245
"""Return a list of matrix types and scalar types."""
31673246
R163 = flint.fmpz_mod_ctx(163)
@@ -4081,6 +4160,8 @@ def test_all_tests():
40814160
test_division_poly,
40824161
test_division_matrix,
40834162

4163+
test_factor_poly_mpoly,
4164+
40844165
test_polys,
40854166
test_mpolys,
40864167

src/flint/types/fmpq_mpoly.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ cdef class fmpq_mpoly(flint_mpoly):
937937
c = fmpz.__new__(fmpz)
938938
fmpz_init_set((<fmpz>c).val, &fac.exp[i])
939939

940-
res[i] = (u, c)
940+
res[i] = (u, int(c))
941941

942942
c = fmpq.__new__(fmpq)
943943
fmpq_set((<fmpq>c).val, fac.constant)

src/flint/types/fmpq_poly.pyx

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -393,26 +393,41 @@ cdef class fmpq_poly(flint_poly):
393393
fmpq_poly_xgcd(res1.val, res2.val, res3.val, self.val, (<fmpq_poly>other).val)
394394
return (res1, res2, res3)
395395

396-
def factor(self):
396+
def factor(self, *, monic=False):
397397
"""
398398
Factors *self* into irreducible polynomials. Returns (*c*, *factors*)
399399
where *c* is the leading coefficient and *factors* is a list of
400-
(*poly*, *exp*) pairs with all *poly* monic.
400+
(*poly*, *exp*).
401401
402402
>>> fmpq_poly.legendre_p(5).factor()
403-
(63/8, [(x, 1), (x^4 + (-10/9)*x^2 + 5/21, 1)])
403+
(1/8, [(x, 1), (63*x^4 + (-70)*x^2 + 15, 1)])
404404
>>> (fmpq_poly([1,-1],10) ** 5 * fmpq_poly([1,2,3],7)).factor()
405+
(-1/700000, [(3*x^2 + 2*x + 1, 1), (x + (-1), 5)])
406+
407+
Since python-flint 0.7.0 this returns primitive denominator-free
408+
factors consistent with ``fmpq_mpoly.factor()``. In previous versions
409+
of python-flint all factors were made monic. Pass ``monic=True`` to get
410+
monic factors instead.
411+
412+
>>> fmpq_poly.legendre_p(5).factor(monic=True)
413+
(63/8, [(x, 1), (x^4 + (-10/9)*x^2 + 5/21, 1)])
414+
>>> (fmpq_poly([1,-1],10) ** 5 * fmpq_poly([1,2,3],7)).factor(monic=True)
405415
(-3/700000, [(x^2 + 2/3*x + 1/3, 1), (x + (-1), 5)])
406416
407417
"""
408418
c, fac = self.numer().factor()
409419
c = fmpq(c)
410-
for i in range(len(fac)):
411-
base, exp = fac[i]
412-
lead = base[base.degree()]
413-
base = fmpq_poly(base, lead)
414-
c *= lead ** exp
415-
fac[i] = (base, exp)
420+
421+
if monic:
422+
for i in range(len(fac)):
423+
base, exp = fac[i]
424+
lead = base[base.degree()]
425+
base = fmpq_poly(base, lead)
426+
c *= lead ** exp
427+
fac[i] = (base, exp)
428+
else:
429+
fac = [(fmpq_poly(f), m) for f, m in fac]
430+
416431
return c / self.denom(), fac
417432

418433
def sqrt(self):

src/flint/types/fmpz_mpoly.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,7 @@ cdef class fmpz_mpoly(flint_mpoly):
920920
c = fmpz.__new__(fmpz)
921921
fmpz_set((<fmpz>c).val, &fac.exp[i])
922922

923-
res[i] = (u, c)
923+
res[i] = (u, int(c))
924924

925925
c = fmpz.__new__(fmpz)
926926
fmpz_set((<fmpz>c).val, fac.constant)

src/flint/types/nmod_poly.pyx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,17 @@ cdef class nmod_poly(flint_poly):
235235
>>> f.leading_coefficient()
236236
133
237237
"""
238+
cdef nmod c
239+
238240
d = self.degree()
239241
if d < 0:
240242
return 0
241-
return nmod_poly_get_coeff_ui(self.val, d)
243+
244+
c = nmod.__new__(nmod)
245+
c.mod = self.val.mod
246+
c.val = nmod_poly_get_coeff_ui(self.val, d)
247+
248+
return c
242249

243250
def inverse_series_trunc(self, slong n):
244251
"""

0 commit comments

Comments
 (0)