Skip to content

Commit 8d4cf40

Browse files
committed
Add factor_squarefree to all poly/mpoly types
1 parent c89ae73 commit 8d4cf40

File tree

9 files changed

+174
-19
lines changed

9 files changed

+174
-19
lines changed

src/flint/test/test_all.py

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3320,16 +3320,52 @@ def _all_polys_mpolys():
33203320
def test_factor_poly_mpoly():
33213321
"""Test that factor() is consistent across different poly/mpoly types."""
33223322

3323-
def factor(p):
3324-
coeff, factors = p.factor()
3323+
def check(p, coeff, factors):
3324+
# Check all the types
33253325
lc = p.leading_coefficient()
33263326
assert type(coeff) is type(lc)
33273327
assert isinstance(factors, list)
33283328
assert all(isinstance(f, tuple) for f in factors)
33293329
for fac, m in factors:
33303330
assert type(fac) is type(p)
33313331
assert type(m) is int
3332-
return coeff, sorted(factors, key=lambda p: (p[1], str(p[0])))
3332+
3333+
# Check the actual factorisation!
3334+
res = coeff
3335+
for fac, m in factors:
3336+
res *= fac ** m
3337+
assert res == p
3338+
3339+
def sort(factors):
3340+
def sort_key(p):
3341+
fac, m = p
3342+
return (m, sorted(str(i) for i in fac.coeffs()))
3343+
return sorted(factors, key=sort_key)
3344+
3345+
def factor(p):
3346+
coeff, factors = p.factor()
3347+
check(p, coeff, factors)
3348+
return coeff, sort(factors)
3349+
3350+
def factor_sqf(p):
3351+
coeff, factors = p.factor_squarefree()
3352+
check(p, coeff, factors)
3353+
return coeff, sort(factors)
3354+
3355+
def sqrt(a):
3356+
if type(x) is flint.fq_default_poly:
3357+
try:
3358+
return S(a).sqrt()
3359+
except ValueError:
3360+
return None
3361+
elif characteristic != 0:
3362+
# XXX: fmpz(0).sqrtmod crashes
3363+
try:
3364+
return flint.fmpz(-1).sqrtmod(characteristic)
3365+
except ValueError:
3366+
return None
3367+
else:
3368+
return None
33333369

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

@@ -3351,9 +3387,40 @@ def factor(p):
33513387
assert factor(x) == (S(1), [(x, 1)])
33523388
assert factor(-x) == (S(-1), [(x, 1)])
33533389
assert factor(x**2) == (S(1), [(x, 2)])
3354-
assert factor(x*(x+1)) == (S(1), [(x, 1), (x+1, 1)])
33553390
assert factor(2*(x+1)) == (S(2), [(x+1, 1)])
33563391

3392+
assert factor_sqf(0*x) == (S(0), [])
3393+
assert factor_sqf(0*x + 1) == (S(1), [])
3394+
assert factor_sqf(0*x + 3) == (S(3), [])
3395+
assert factor_sqf(-x) == (S(-1), [(x, 1)])
3396+
assert factor_sqf(x**2) == (S(1), [(x, 2)])
3397+
assert factor_sqf(2*(x+1)) == (S(2), [(x+1, 1)])
3398+
3399+
assert factor(x*(x+1)) == (S(1), [(x, 1), (x+1, 1)])
3400+
if y is None:
3401+
assert factor_sqf(x*(x+1)) == (S(1), [(x**2+x, 1)])
3402+
else:
3403+
# mpoly types have a different squarefree factorisation because
3404+
# they handle trivial factors differently...
3405+
#
3406+
# Maybe it is worth making them consistent by absorbing the power
3407+
# of x into a factor of equal multiplicity.
3408+
assert factor_sqf(x*(x+1)) == (S(1), [(x, 1), (x+1, 1)])
3409+
3410+
assert factor_sqf(x**2*(x+1)) == (S(1), [(x+1, 1), (x, 2)])
3411+
3412+
assert factor((x-1)*(x+1)) == (S(1), sort([(x-1, 1), (x+1, 1)]))
3413+
assert factor_sqf((x-1)*(x+1)) == (S(1), [(x**2-1, 1)])
3414+
3415+
p = 3*(x-1)**2*(x+1)**2*(x**2 + 1)**3
3416+
assert factor_sqf(p) == (S(3), [(x**2 - 1, 2), (x**2 + 1, 3)])
3417+
3418+
i = sqrt(-1)
3419+
if i is not None:
3420+
assert factor(p) == (S(3), sort([(x+1, 2), (x-1, 2), (x+i, 3), (x-i, 3)]))
3421+
else:
3422+
assert factor(p) == (S(3), sort([(x-1, 2), (x+1, 2), (x**2+1, 3)]))
3423+
33573424
if characteristic == 0:
33583425
# primitive factors over Z for Z and Q.
33593426
assert factor(2*x+1) == (S(1), [(2*x+1, 1)])
@@ -3372,16 +3439,20 @@ def factor(p):
33723439
assert factor(x*y+1) == (S(1), [(x*y+1, 1)])
33733440
assert factor(x*y) == (S(1), [(x, 1), (y, 1)])
33743441

3442+
assert factor_sqf((x*y+1)**2*(x*y-1)) == (S(1), [(x*y-1, 1), (x*y+1, 2)])
3443+
3444+
p = 2*x + y
33753445
if characteristic == 0:
3376-
assert factor(2*x + y) == (S(1), [(2*x + y, 1)])
3446+
assert factor(p) == factor_sqf(p) == (S(1), [(p, 1)])
33773447
else:
3378-
assert factor(2*x + y) == (S(2), [(x + y/2, 1)])
3448+
assert factor(p) == factor_sqf(p) == (S(2), [(p/2, 1)])
33793449

33803450
if is_field:
3451+
p = (2*x + y)/7
33813452
if characteristic == 0:
3382-
assert factor((2*x+y)/7) == (S(1)/7, [(2*x+y, 1)])
3453+
assert factor(p) == factor_sqf(p) == (S(1)/7, [(7*p, 1)])
33833454
else:
3384-
assert factor((2*x+y)/7) == (S(2)/7, [(x+y/2, 1)])
3455+
assert factor(p) == factor_sqf(p) == (S(2)/7, [(7*p/2, 1)])
33853456

33863457

33873458
def _all_matrices():

src/flint/types/fmpq_mpoly.pyx

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

988-
res[i] = (u, c)
988+
res[i] = (u, int(c))
989989

990990
c = fmpq.__new__(fmpq)
991991
fmpq_set((<fmpq>c).val, fac.constant)

src/flint/types/fmpq_poly.pyx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,27 @@ cdef class fmpq_poly(flint_poly):
448448

449449
return c / self.denom(), fac
450450

451+
def factor_squarefree(self):
452+
"""
453+
Factors *self* into square-free polynomials. Returns (*c*, *factors*)
454+
where *c* is the leading coefficient and *factors* is a list of
455+
(*poly*, *exp*).
456+
457+
>>> x = fmpq_poly([0, 1])
458+
>>> p = x**2 * (x/2 - 1)**2 * (x + 1)**3
459+
>>> p
460+
1/4*x^7 + (-1/4)*x^6 + (-5/4)*x^5 + 1/4*x^4 + 2*x^3 + x^2
461+
>>> p.factor_squarefree()
462+
(1/4, [(x**2 - 2*x, 2), (x + 1, 3)])
463+
>>> p.factor()
464+
(1/4, [(x, 2), (x + (-2), 2), (x + 1, 3)])
465+
466+
"""
467+
c, fac = self.numer().factor_squarefree()
468+
c = fmpq(c) / self.denom()
469+
fac = [(fmpq_poly(f), m) for f, m in fac]
470+
return c, fac
471+
451472
def sqrt(self):
452473
"""
453474
Return the exact square root of this polynomial or ``None``.

src/flint/types/fmpz.pyx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,7 @@ cdef class fmpz(flint_scalar):
900900
return u, v
901901

902902
# warning: m should be prime!
903+
# also if self is zero this crashes...
903904
def sqrtmod(self, m):
904905
cdef fmpz v
905906
v = fmpz()

src/flint/types/fmpz_mod_mpoly.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,7 @@ cdef class fmpz_mod_mpoly(flint_mpoly):
896896
c = fmpz.__new__(fmpz)
897897
fmpz_set((<fmpz>c).val, &fac.exp[i])
898898

899-
res[i] = (u, c)
899+
res[i] = (u, int(c))
900900

901901
c = fmpz.__new__(fmpz)
902902
fmpz_set((<fmpz>c).val, fac.constant)

src/flint/types/fmpz_mpoly.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ cdef class fmpz_mpoly(flint_mpoly):
934934

935935
def factor_squarefree(self):
936936
"""
937-
Factors self into irreducible factors, returning a tuple
937+
Factors self into square-free factors, returning a tuple
938938
(c, factors) where c is the content of the coefficients and
939939
factors is a list of (poly, exp) pairs.
940940
@@ -965,7 +965,7 @@ cdef class fmpz_mpoly(flint_mpoly):
965965
c = fmpz.__new__(fmpz)
966966
fmpz_set((<fmpz>c).val, &fac.exp[i])
967967

968-
res[i] = (u, c)
968+
res[i] = (u, int(c))
969969

970970
c = fmpz.__new__(fmpz)
971971
fmpz_set((<fmpz>c).val, fac.constant)

src/flint/types/fmpz_poly.pyx

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,11 +384,36 @@ cdef class fmpz_poly(flint_poly):
384384
(1, [(6*x^5 + 5*x^4 + 4*x^3 + 3*x^2 + 2*x + 1, 1)])
385385
386386
"""
387+
return self._factor('irreducible')
388+
389+
def factor_squarefree(self):
390+
"""
391+
Factors self into square-free factors, returning a tuple
392+
(c, factors) where c is the content of the coefficients and
393+
factors is a list of (poly, exp) pairs.
394+
395+
>>> x = fmpz_poly([0, 1])
396+
>>> p = (-3 * x**2 * (x + 1)**2 * (x - 1)**3)
397+
>>> p.factor_squarefree()
398+
(-3, [(x**2 + x, 2), (x - 1, 3)])
399+
>>> p.factor()
400+
(-3, [(x, 2), (x + 1, 2), (x - 1, 3)])
401+
402+
"""
403+
return self._factor('squarefree')
404+
405+
def _factor(self, factor_type):
387406
cdef fmpz_poly_factor_t fac
388407
cdef int i
389408
fmpz_poly_factor_init(fac)
390-
# should use fmpz_poly_factor, but not available in 2.5.2
391-
fmpz_poly_factor(fac, self.val)
409+
410+
if factor_type == 'squarefree':
411+
fmpz_poly_factor_squarefree(fac, self.val)
412+
elif factor_type == 'irreducible':
413+
fmpz_poly_factor(fac, self.val)
414+
else:
415+
assert False
416+
392417
res = [0] * fac.num
393418
for 0 <= i < fac.num:
394419
u = fmpz_poly.__new__(fmpz_poly)
@@ -398,6 +423,7 @@ cdef class fmpz_poly(flint_poly):
398423
c = fmpz.__new__(fmpz)
399424
fmpz_set((<fmpz>c).val, &fac.c)
400425
fmpz_poly_factor_clear(fac)
426+
401427
return c, res
402428

403429
def complex_roots(self, bint verbose=False):

src/flint/types/nmod_mpoly.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -871,9 +871,9 @@ cdef class nmod_mpoly(flint_mpoly):
871871
c = fmpz.__new__(fmpz)
872872
fmpz_set((<fmpz>c).val, &fac.exp[i])
873873

874-
res[i] = (u, c)
874+
res[i] = (u, int(c))
875875

876-
constant = fac.constant
876+
constant = nmod(fac.constant, self.ctx.modulus())
877877
nmod_mpoly_factor_clear(fac, self.ctx.val)
878878
return constant, res
879879

src/flint/types/nmod_poly.pyx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -632,16 +632,49 @@ cdef class nmod_poly(flint_poly):
632632
(3, [(x + 4, 1), (x + 2, 1), (x^2 + 4*x + 1, 1)])
633633
634634
"""
635+
if algorithm is None:
636+
algorithm = 'irreducible'
637+
elif algorithm not in ('berlekamp', 'cantor-zassenhaus'):
638+
raise ValueError(f"unknown factorization algorithm: {algorithm}")
639+
return self._factor(algorithm)
640+
641+
def factor_squarefree(self):
642+
"""
643+
Factors *self* into square-free polynomials. Returns (*c*, *factors*)
644+
where *c* is the leading coefficient and *factors* is a list of
645+
(*poly*, *exp*).
646+
647+
>>> x = nmod_poly([0, 1], 7)
648+
>>> p = x**2 * (x/2 - 1)**2 * (x + 1)**3
649+
>>> p
650+
2*x^7 + 5*x^6 + 4*x^5 + 2*x^4 + 2*x^3 + x^2
651+
>>> p.factor_squarefree()
652+
(2, [(x**2 - 2*x, 2), (x + 1, 3)])
653+
>>> p.factor()
654+
(1/4, [(x, 2), (x + 5, 2), (x + 1, 3)])
655+
656+
"""
657+
return self._factor('squarefree')
658+
659+
def _factor(self, factor_type):
635660
cdef nmod_poly_factor_t fac
636661
cdef mp_limb_t lead
637662
cdef int i
663+
638664
nmod_poly_factor_init(fac)
639-
if algorithm == 'berlekamp':
665+
666+
if factor_type == 'berlekamp':
640667
lead = nmod_poly_factor_with_berlekamp(fac, self.val)
641-
elif algorithm == 'cantor-zassenhaus':
668+
elif factor_type == 'cantor-zassenhaus':
642669
lead = nmod_poly_factor_with_cantor_zassenhaus(fac, self.val)
643-
else:
670+
elif factor_type == 'irreducible':
644671
lead = nmod_poly_factor(fac, self.val)
672+
elif factor_type == 'squarefree':
673+
nmod_poly_factor_squarefree(fac, self.val)
674+
lead = (<nmod>self.leading_coefficient()).val
675+
else:
676+
assert False
677+
645678
res = [None] * fac.num
646679
for 0 <= i < fac.num:
647680
u = nmod_poly.__new__(nmod_poly)
@@ -650,10 +683,13 @@ cdef class nmod_poly(flint_poly):
650683
nmod_poly_set((<nmod_poly>u).val, &fac.p[i])
651684
exp = fac.exp[i]
652685
res[i] = (u, exp)
686+
653687
c = nmod.__new__(nmod)
654688
(<nmod>c).mod = self.val.mod
655689
(<nmod>c).val = lead
690+
656691
nmod_poly_factor_clear(fac)
692+
657693
return c, res
658694

659695
def sqrt(nmod_poly self):

0 commit comments

Comments
 (0)