Skip to content

Commit 8c4cc87

Browse files
committed
Make fmpq_poly.factor() return primitive factors
1 parent 464a276 commit 8c4cc87

File tree

6 files changed

+123
-15
lines changed

6 files changed

+123
-15
lines changed

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
@@ -249,9 +250,13 @@ cdef class flint_poly(flint_elem):
249250
roots = []
250251
factors = self.factor()
251252
for fac, m in factors[1]:
252-
if fac.degree() == fac[1] == 1:
253-
v = - fac[0]
254-
roots.append((v, m))
253+
if fac.degree() == 1:
254+
try:
255+
v = - fac[0] / fac[1]
256+
except DomainError:
257+
pass
258+
else:
259+
roots.append((v, m))
255260
return roots
256261

257262
def complex_roots(self):

src/flint/test/test_all.py

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

32353235

3236+
def _all_polys_mpolys():
3237+
3238+
for P, S, is_field in _all_polys():
3239+
x = P([0, 1])
3240+
y = None
3241+
assert isinstance(x, (
3242+
flint.fmpz_poly,
3243+
flint.fmpq_poly,
3244+
flint.nmod_poly,
3245+
flint.fmpz_mod_poly,
3246+
flint.fq_default_poly,
3247+
))
3248+
characteristic_zero = isinstance(x, (flint.fmpz_poly, flint.fmpq_poly))
3249+
yield P, S, [x, y], is_field, characteristic_zero
3250+
3251+
for P, ctx_type, S, is_field in _all_mpolys():
3252+
ctx = ctx_type(2, flint.Ordering.lex, ["x", "y"])
3253+
x, y = ctx.gens()
3254+
assert isinstance(x, (
3255+
flint.fmpz_mpoly,
3256+
flint.fmpq_mpoly,
3257+
))
3258+
characteristic_zero = isinstance(x, (flint.fmpz_mpoly, flint.fmpq_mpoly))
3259+
yield P, S, [x, y], is_field, characteristic_zero
3260+
3261+
3262+
def test_factor_poly_mpoly():
3263+
"""Test that factor() is consistent across different poly/mpoly types."""
3264+
3265+
def factor(p):
3266+
coeff, factors = p.factor()
3267+
try:
3268+
lc = p.leading_coefficient()
3269+
except AttributeError:
3270+
# XXX: Not all univariate types have a leading_coefficient method.
3271+
lc = p[0]
3272+
assert type(coeff) is type(lc)
3273+
assert isinstance(factors, list)
3274+
assert all(isinstance(f, tuple) for f in factors)
3275+
for fac, m in factors:
3276+
assert type(fac) is type(p)
3277+
assert type(m) is int
3278+
return coeff, sorted(factors, key=lambda p: (p[1], str(p[0])))
3279+
3280+
for P, S, [x, y], is_field, characteristic_zero in _all_polys_mpolys():
3281+
3282+
assert factor(x) == (S(1), [(x, 1)])
3283+
assert factor(x**2) == (S(1), [(x, 2)])
3284+
assert factor(x*(x+1)) == (S(1), [(x, 1), (x+1, 1)])
3285+
assert factor(2*(x+1)) == (S(2), [(x+1, 1)])
3286+
3287+
if characteristic_zero:
3288+
# primitive factors over Z for Z and Q.
3289+
assert factor(2*x+1) == (S(1), [(2*x+1, 1)])
3290+
else:
3291+
# monic factors over Z/pZ and GF(p^d)
3292+
assert factor(2*x+1) == (S(2), [(x+S(1)/2, 1)])
3293+
3294+
if is_field:
3295+
if characteristic_zero:
3296+
# primitive factors over Z for Z and Q.
3297+
assert factor((2*x+1)/7) == (S(1)/7, [(2*x+1, 1)])
3298+
else:
3299+
# monic factors over Z/pZ and GF(p^d)
3300+
assert factor((2*x+1)/7) == (S(2)/7, [(x+S(1)/2, 1)])
3301+
3302+
if y is not None:
3303+
3304+
assert factor(x*y+1) == (S(1), [(x*y+1, 1)])
3305+
assert factor(x*y) == (S(1), [(x, 1), (y, 1)])
3306+
3307+
if characteristic_zero:
3308+
# primitive factors over Z for Z and Q.
3309+
assert factor(2*x + y) == (S(1), [(2*x + y, 1)])
3310+
else:
3311+
# monic factors over Z/pZ and GF(p^d)
3312+
assert factor(2*x + y) == (S(1)/2, [(x + y/2, 1)])
3313+
3314+
32363315
def _all_matrices():
32373316
"""Return a list of matrix types and scalar types."""
32383317
R163 = flint.fmpz_mod_ctx(163)
@@ -4152,6 +4231,8 @@ def test_all_tests():
41524231
test_division_poly,
41534232
test_division_matrix,
41544233

4234+
test_factor_poly_mpoly,
4235+
41554236
test_polys,
41564237
test_mpolys,
41574238

src/flint/types/fmpq_mpoly.pyx

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

943-
res[i] = (u, c)
943+
res[i] = (u, int(c))
944944

945945
c = fmpq.__new__(fmpq)
946946
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
@@ -919,7 +919,7 @@ cdef class fmpz_mpoly(flint_mpoly):
919919
c = fmpz.__new__(fmpz)
920920
fmpz_set((<fmpz>c).val, &fac.exp[i])
921921

922-
res[i] = (u, c)
922+
res[i] = (u, int(c))
923923

924924
c = fmpz.__new__(fmpz)
925925
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)