Skip to content

Commit f2bda3c

Browse files
Merge pull request #189 from oscarbenjamin/pr_fmpq_poly_factor_prim
Make fmpq_poly.factor() return primitive factors
2 parents 464a276 + e4f2e8d commit f2bda3c

17 files changed

+823
-119
lines changed

src/flint/flint_base/flint_base.pyx

+8-3
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/flintlib/nmod_poly_factor.pxd

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ cdef extern from "flint/nmod_poly_factor.h":
66

77
ctypedef struct nmod_poly_factor_struct:
88
nmod_poly_struct *p
9-
long *exp
10-
long num
11-
long alloc
9+
slong *exp
10+
slong num
11+
slong alloc
1212
ctypedef nmod_poly_factor_struct nmod_poly_factor_t[1]
1313

1414
# from here on is parsed

src/flint/test/test_all.py

+353-53
Large diffs are not rendered by default.

src/flint/types/fmpq.pyx

+14
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,17 @@ cdef class fmpq(flint_scalar):
486486
return v
487487
else:
488488
raise OverflowError("fmpq_pow_fmpz(): exponent too large")
489+
490+
def sqrt(self):
491+
"""
492+
Return exact rational square root of self or raise an error.
493+
494+
>>> fmpq(9, 4).sqrt()
495+
3/2
496+
>>> fmpq(8).sqrt()
497+
Traceback (most recent call last):
498+
...
499+
flint.utils.flint_exceptions.DomainError: not a square number
500+
501+
"""
502+
return fmpq(self.numer().sqrt(), self.denom().sqrt())

src/flint/types/fmpq_mpoly.pyx

+22-3
Original file line numberDiff line numberDiff line change
@@ -858,6 +858,25 @@ cdef class fmpq_mpoly(flint_mpoly):
858858
fmpq_mpoly_total_degree_fmpz((<fmpz> res).val, self.val, self.ctx.val)
859859
return res
860860

861+
def leading_coefficient(self):
862+
"""
863+
Leading coefficient in the monomial ordering.
864+
865+
>>> from flint import Ordering
866+
>>> ctx = fmpq_mpoly_ctx(2, Ordering.lex, ['x', 'y'])
867+
>>> x, y = ctx.gens()
868+
>>> p = 2*x*y + 3*x + 4*y**2 + 5
869+
>>> p
870+
2*x*y + 3*x + 4*y^2 + 5
871+
>>> p.leading_coefficient()
872+
2
873+
874+
"""
875+
if fmpq_mpoly_is_zero(self.val, self.ctx.val):
876+
return fmpq(0)
877+
else:
878+
return self.coefficient(0)
879+
861880
def repr(self):
862881
return f"{self.ctx}.from_dict({self.to_dict()})"
863882

@@ -906,7 +925,7 @@ cdef class fmpq_mpoly(flint_mpoly):
906925
if fmpq_mpoly_sqrt(res.val, self.val, self.ctx.val):
907926
return res
908927
else:
909-
raise ValueError("polynomial is not a perfect square")
928+
raise DomainError("polynomial is not a perfect square")
910929

911930
def factor(self):
912931
"""
@@ -940,7 +959,7 @@ cdef class fmpq_mpoly(flint_mpoly):
940959
c = fmpz.__new__(fmpz)
941960
fmpz_init_set((<fmpz>c).val, &fac.exp[i])
942961

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

945964
c = fmpq.__new__(fmpq)
946965
fmpq_set((<fmpq>c).val, fac.constant)
@@ -979,7 +998,7 @@ cdef class fmpq_mpoly(flint_mpoly):
979998
c = fmpz.__new__(fmpz)
980999
fmpz_init_set((<fmpz>c).val, &fac.exp[i])
9811000

982-
res[i] = (u, c)
1001+
res[i] = (u, int(c))
9831002

9841003
c = fmpq.__new__(fmpq)
9851004
fmpq_set((<fmpq>c).val, fac.constant)

src/flint/types/fmpq_poly.pyx

+63-9
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,24 @@ cdef class fmpq_poly(flint_poly):
176176
def is_one(self):
177177
return <bint>fmpq_poly_is_one(self.val)
178178

179+
def leading_coefficient(self):
180+
"""
181+
Returns the leading coefficient of the polynomial.
182+
183+
>>> f = fmpq_poly([1, 2, 3])
184+
>>> f
185+
3*x^2 + 2*x + 1
186+
>>> f.leading_coefficient()
187+
3
188+
"""
189+
cdef fmpq x
190+
cdef slong d
191+
d = fmpq_poly_degree(self.val)
192+
x = fmpq.__new__(fmpq)
193+
if d >= 0:
194+
fmpq_poly_get_coeff_fmpq(x.val, self.val, d)
195+
return x
196+
179197
def __call__(self, other):
180198
t = any_as_fmpz(other)
181199
if t is not NotImplemented:
@@ -393,28 +411,64 @@ cdef class fmpq_poly(flint_poly):
393411
fmpq_poly_xgcd(res1.val, res2.val, res3.val, self.val, (<fmpq_poly>other).val)
394412
return (res1, res2, res3)
395413

396-
def factor(self):
414+
def factor(self, *, monic=False):
397415
"""
398416
Factors *self* into irreducible polynomials. Returns (*c*, *factors*)
399417
where *c* is the leading coefficient and *factors* is a list of
400-
(*poly*, *exp*) pairs with all *poly* monic.
418+
(*poly*, *exp*).
401419
402420
>>> fmpq_poly.legendre_p(5).factor()
403-
(63/8, [(x, 1), (x^4 + (-10/9)*x^2 + 5/21, 1)])
421+
(1/8, [(x, 1), (63*x^4 + (-70)*x^2 + 15, 1)])
404422
>>> (fmpq_poly([1,-1],10) ** 5 * fmpq_poly([1,2,3],7)).factor()
423+
(-1/700000, [(3*x^2 + 2*x + 1, 1), (x + (-1), 5)])
424+
425+
Since python-flint 0.7.0 this returns primitive denominator-free
426+
factors consistent with ``fmpq_mpoly.factor()``. In previous versions
427+
of python-flint all factors were made monic. Pass ``monic=True`` to get
428+
monic factors instead.
429+
430+
>>> fmpq_poly.legendre_p(5).factor(monic=True)
431+
(63/8, [(x, 1), (x^4 + (-10/9)*x^2 + 5/21, 1)])
432+
>>> (fmpq_poly([1,-1],10) ** 5 * fmpq_poly([1,2,3],7)).factor(monic=True)
405433
(-3/700000, [(x^2 + 2/3*x + 1/3, 1), (x + (-1), 5)])
406434
407435
"""
408436
c, fac = self.numer().factor()
409437
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)
438+
439+
if monic:
440+
for i in range(len(fac)):
441+
base, exp = fac[i]
442+
lead = base[base.degree()]
443+
base = fmpq_poly(base, lead)
444+
c *= lead ** exp
445+
fac[i] = (base, exp)
446+
else:
447+
fac = [(fmpq_poly(f), m) for f, m in fac]
448+
416449
return c / self.denom(), fac
417450

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+
418472
def sqrt(self):
419473
"""
420474
Return the exact square root of this polynomial or ``None``.

src/flint/types/fmpz.pyx

+79-6
Original file line numberDiff line numberDiff line change
@@ -883,29 +883,102 @@ cdef class fmpz(flint_scalar):
883883
return self.bit_length()
884884

885885
def isqrt(self):
886+
"""
887+
Return square root rounded down.
888+
889+
>>> fmpz(9).isqrt()
890+
3
891+
>>> fmpz(8).isqrt()
892+
2
893+
894+
"""
895+
cdef fmpz v
896+
897+
if fmpz_sgn(self.val) < 0:
898+
raise DomainError("integer square root of a negative number")
899+
900+
v = fmpz()
901+
fmpz_sqrt(v.val, self.val)
902+
return v
903+
904+
def sqrt(self):
905+
"""
906+
Return exact integer square root of self or raise an error.
907+
908+
>>> fmpz(9).sqrt()
909+
3
910+
>>> fmpz(8).sqrt()
911+
Traceback (most recent call last):
912+
...
913+
flint.utils.flint_exceptions.DomainError: not a square number
914+
915+
"""
886916
cdef fmpz v
917+
887918
if fmpz_sgn(self.val) < 0:
888-
raise ValueError("integer square root of a negative number")
919+
raise DomainError("integer square root of a negative number")
920+
889921
v = fmpz()
890922
fmpz_sqrt(v.val, self.val)
923+
924+
c = fmpz()
925+
fmpz_mul(c.val, v.val, v.val)
926+
if not fmpz_equal(c.val, self.val):
927+
raise DomainError("not a square number")
928+
891929
return v
892930

893931
def sqrtrem(self):
932+
"""
933+
Return the integer square root of self and remainder.
934+
935+
>>> fmpz(9).sqrtrem()
936+
(3, 0)
937+
>>> fmpz(8).sqrtrem()
938+
(2, 4)
939+
>>> c = fmpz(123456789012345678901234567890)
940+
>>> u, v = c.sqrtrem()
941+
>>> u ** 2 + v == c
942+
True
943+
944+
"""
894945
cdef fmpz u, v
946+
895947
if fmpz_sgn(self.val) < 0:
896-
raise ValueError("integer square root of a negative number")
948+
raise DomainError("integer square root of a negative number")
949+
897950
u = fmpz()
898951
v = fmpz()
899952
fmpz_sqrtrem(u.val, v.val, self.val)
953+
900954
return u, v
901955

902956
# warning: m should be prime!
903-
def sqrtmod(self, m):
957+
def sqrtmod(self, p):
958+
"""
959+
Return modular square root of self modulo *p* or raise an error.
960+
961+
>>> fmpz(10).sqrtmod(13)
962+
6
963+
>>> (6**2) % 13
964+
10
965+
>>> fmpz(11).sqrtmod(13)
966+
Traceback (most recent call last):
967+
...
968+
flint.utils.flint_exceptions.DomainError: modular square root does not exist
969+
970+
The modulus *p* must be a prime number.
971+
"""
904972
cdef fmpz v
973+
905974
v = fmpz()
906-
m = fmpz(m)
907-
if not fmpz_sqrtmod(v.val, self.val, (<fmpz>m).val):
908-
raise ValueError("unable to compute modular square root")
975+
if fmpz_is_zero(self.val):
976+
return v
977+
978+
p = fmpz(p)
979+
if not fmpz_sqrtmod(v.val, self.val, (<fmpz>p).val):
980+
raise DomainError("modular square root does not exist")
981+
909982
return v
910983

911984
def root(self, long n):

src/flint/types/fmpz_mod.pyx

+35
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ from flint.flintlib.fmpz cimport(
1010
fmpz_is_probabprime,
1111
fmpz_mul,
1212
fmpz_invmod,
13+
fmpz_sqrtmod,
1314
fmpz_divexact,
1415
fmpz_gcd,
1516
fmpz_is_one,
@@ -29,6 +30,9 @@ from flint.types.fmpz cimport(
2930
cimport cython
3031
cimport libc.stdlib
3132

33+
from flint.utils.flint_exceptions import DomainError
34+
35+
3236
cdef class fmpz_mod_ctx:
3337
r"""
3438
Context object for creating :class:`~.fmpz_mod` initalised
@@ -578,3 +582,34 @@ cdef class fmpz_mod(flint_scalar):
578582
)
579583

580584
return res
585+
586+
def sqrt(self):
587+
"""
588+
Return the square root of this ``fmpz_mod`` or raise an exception.
589+
590+
>>> ctx = fmpz_mod_ctx(13)
591+
>>> s = ctx(10).sqrt()
592+
>>> s
593+
fmpz_mod(6, 13)
594+
>>> s * s
595+
fmpz_mod(10, 13)
596+
>>> ctx(11).sqrt()
597+
Traceback (most recent call last):
598+
...
599+
flint.utils.flint_exceptions.DomainError: no square root exists for 11 mod 13
600+
601+
The modulus must be prime.
602+
603+
"""
604+
cdef fmpz_mod v
605+
606+
v = fmpz_mod.__new__(fmpz_mod)
607+
v.ctx = self.ctx
608+
609+
if fmpz_is_zero(self.val):
610+
return v
611+
612+
if not fmpz_sqrtmod(v.val, self.val, self.ctx.val.n):
613+
raise DomainError("no square root exists for {} mod {}".format(self, self.ctx.modulus()))
614+
615+
return v

0 commit comments

Comments
 (0)