You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2278 lines
57 KiB
2278 lines
57 KiB
from sympy.core.symbol import Dummy
|
|
from sympy.ntheory import nextprime
|
|
from sympy.ntheory.modular import crt
|
|
from sympy.polys.domains import PolynomialRing
|
|
from sympy.polys.galoistools import (
|
|
gf_gcd, gf_from_dict, gf_gcdex, gf_div, gf_lcm)
|
|
from sympy.polys.polyerrors import ModularGCDFailed
|
|
|
|
from mpmath import sqrt
|
|
import random
|
|
|
|
|
|
def _trivial_gcd(f, g):
|
|
"""
|
|
Compute the GCD of two polynomials in trivial cases, i.e. when one
|
|
or both polynomials are zero.
|
|
"""
|
|
ring = f.ring
|
|
|
|
if not (f or g):
|
|
return ring.zero, ring.zero, ring.zero
|
|
elif not f:
|
|
if g.LC < ring.domain.zero:
|
|
return -g, ring.zero, -ring.one
|
|
else:
|
|
return g, ring.zero, ring.one
|
|
elif not g:
|
|
if f.LC < ring.domain.zero:
|
|
return -f, -ring.one, ring.zero
|
|
else:
|
|
return f, ring.one, ring.zero
|
|
return None
|
|
|
|
|
|
def _gf_gcd(fp, gp, p):
|
|
r"""
|
|
Compute the GCD of two univariate polynomials in `\mathbb{Z}_p[x]`.
|
|
"""
|
|
dom = fp.ring.domain
|
|
|
|
while gp:
|
|
rem = fp
|
|
deg = gp.degree()
|
|
lcinv = dom.invert(gp.LC, p)
|
|
|
|
while True:
|
|
degrem = rem.degree()
|
|
if degrem < deg:
|
|
break
|
|
rem = (rem - gp.mul_monom((degrem - deg,)).mul_ground(lcinv * rem.LC)).trunc_ground(p)
|
|
|
|
fp = gp
|
|
gp = rem
|
|
|
|
return fp.mul_ground(dom.invert(fp.LC, p)).trunc_ground(p)
|
|
|
|
|
|
def _degree_bound_univariate(f, g):
|
|
r"""
|
|
Compute an upper bound for the degree of the GCD of two univariate
|
|
integer polynomials `f` and `g`.
|
|
|
|
The function chooses a suitable prime `p` and computes the GCD of
|
|
`f` and `g` in `\mathbb{Z}_p[x]`. The choice of `p` guarantees that
|
|
the degree in `\mathbb{Z}_p[x]` is greater than or equal to the degree
|
|
in `\mathbb{Z}[x]`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
univariate integer polynomial
|
|
g : PolyElement
|
|
univariate integer polynomial
|
|
|
|
"""
|
|
gamma = f.ring.domain.gcd(f.LC, g.LC)
|
|
p = 1
|
|
|
|
p = nextprime(p)
|
|
while gamma % p == 0:
|
|
p = nextprime(p)
|
|
|
|
fp = f.trunc_ground(p)
|
|
gp = g.trunc_ground(p)
|
|
hp = _gf_gcd(fp, gp, p)
|
|
deghp = hp.degree()
|
|
return deghp
|
|
|
|
|
|
def _chinese_remainder_reconstruction_univariate(hp, hq, p, q):
|
|
r"""
|
|
Construct a polynomial `h_{pq}` in `\mathbb{Z}_{p q}[x]` such that
|
|
|
|
.. math ::
|
|
|
|
h_{pq} = h_p \; \mathrm{mod} \, p
|
|
|
|
h_{pq} = h_q \; \mathrm{mod} \, q
|
|
|
|
for relatively prime integers `p` and `q` and polynomials
|
|
`h_p` and `h_q` in `\mathbb{Z}_p[x]` and `\mathbb{Z}_q[x]`
|
|
respectively.
|
|
|
|
The coefficients of the polynomial `h_{pq}` are computed with the
|
|
Chinese Remainder Theorem. The symmetric representation in
|
|
`\mathbb{Z}_p[x]`, `\mathbb{Z}_q[x]` and `\mathbb{Z}_{p q}[x]` is used.
|
|
It is assumed that `h_p` and `h_q` have the same degree.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
hp : PolyElement
|
|
univariate integer polynomial with coefficients in `\mathbb{Z}_p`
|
|
hq : PolyElement
|
|
univariate integer polynomial with coefficients in `\mathbb{Z}_q`
|
|
p : Integer
|
|
modulus of `h_p`, relatively prime to `q`
|
|
q : Integer
|
|
modulus of `h_q`, relatively prime to `p`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import _chinese_remainder_reconstruction_univariate
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x = ring("x", ZZ)
|
|
>>> p = 3
|
|
>>> q = 5
|
|
|
|
>>> hp = -x**3 - 1
|
|
>>> hq = 2*x**3 - 2*x**2 + x
|
|
|
|
>>> hpq = _chinese_remainder_reconstruction_univariate(hp, hq, p, q)
|
|
>>> hpq
|
|
2*x**3 + 3*x**2 + 6*x + 5
|
|
|
|
>>> hpq.trunc_ground(p) == hp
|
|
True
|
|
>>> hpq.trunc_ground(q) == hq
|
|
True
|
|
|
|
"""
|
|
n = hp.degree()
|
|
x = hp.ring.gens[0]
|
|
hpq = hp.ring.zero
|
|
|
|
for i in range(n+1):
|
|
hpq[(i,)] = crt([p, q], [hp.coeff(x**i), hq.coeff(x**i)], symmetric=True)[0]
|
|
|
|
hpq.strip_zero()
|
|
return hpq
|
|
|
|
|
|
def modgcd_univariate(f, g):
|
|
r"""
|
|
Computes the GCD of two polynomials in `\mathbb{Z}[x]` using a modular
|
|
algorithm.
|
|
|
|
The algorithm computes the GCD of two univariate integer polynomials
|
|
`f` and `g` by computing the GCD in `\mathbb{Z}_p[x]` for suitable
|
|
primes `p` and then reconstructing the coefficients with the Chinese
|
|
Remainder Theorem. Trial division is only made for candidates which
|
|
are very likely the desired GCD.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
univariate integer polynomial
|
|
g : PolyElement
|
|
univariate integer polynomial
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
GCD of the polynomials `f` and `g`
|
|
cff : PolyElement
|
|
cofactor of `f`, i.e. `\frac{f}{h}`
|
|
cfg : PolyElement
|
|
cofactor of `g`, i.e. `\frac{g}{h}`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import modgcd_univariate
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x = ring("x", ZZ)
|
|
|
|
>>> f = x**5 - 1
|
|
>>> g = x - 1
|
|
|
|
>>> h, cff, cfg = modgcd_univariate(f, g)
|
|
>>> h, cff, cfg
|
|
(x - 1, x**4 + x**3 + x**2 + x + 1, 1)
|
|
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
>>> f = 6*x**2 - 6
|
|
>>> g = 2*x**2 + 4*x + 2
|
|
|
|
>>> h, cff, cfg = modgcd_univariate(f, g)
|
|
>>> h, cff, cfg
|
|
(2*x + 2, 3*x - 3, x + 1)
|
|
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Monagan00]_
|
|
|
|
"""
|
|
assert f.ring == g.ring and f.ring.domain.is_ZZ
|
|
|
|
result = _trivial_gcd(f, g)
|
|
if result is not None:
|
|
return result
|
|
|
|
ring = f.ring
|
|
|
|
cf, f = f.primitive()
|
|
cg, g = g.primitive()
|
|
ch = ring.domain.gcd(cf, cg)
|
|
|
|
bound = _degree_bound_univariate(f, g)
|
|
if bound == 0:
|
|
return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
|
|
|
|
gamma = ring.domain.gcd(f.LC, g.LC)
|
|
m = 1
|
|
p = 1
|
|
|
|
while True:
|
|
p = nextprime(p)
|
|
while gamma % p == 0:
|
|
p = nextprime(p)
|
|
|
|
fp = f.trunc_ground(p)
|
|
gp = g.trunc_ground(p)
|
|
hp = _gf_gcd(fp, gp, p)
|
|
deghp = hp.degree()
|
|
|
|
if deghp > bound:
|
|
continue
|
|
elif deghp < bound:
|
|
m = 1
|
|
bound = deghp
|
|
continue
|
|
|
|
hp = hp.mul_ground(gamma).trunc_ground(p)
|
|
if m == 1:
|
|
m = p
|
|
hlastm = hp
|
|
continue
|
|
|
|
hm = _chinese_remainder_reconstruction_univariate(hp, hlastm, p, m)
|
|
m *= p
|
|
|
|
if not hm == hlastm:
|
|
hlastm = hm
|
|
continue
|
|
|
|
h = hm.quo_ground(hm.content())
|
|
fquo, frem = f.div(h)
|
|
gquo, grem = g.div(h)
|
|
if not frem and not grem:
|
|
if h.LC < 0:
|
|
ch = -ch
|
|
h = h.mul_ground(ch)
|
|
cff = fquo.mul_ground(cf // ch)
|
|
cfg = gquo.mul_ground(cg // ch)
|
|
return h, cff, cfg
|
|
|
|
|
|
def _primitive(f, p):
|
|
r"""
|
|
Compute the content and the primitive part of a polynomial in
|
|
`\mathbb{Z}_p[x_0, \ldots, x_{k-2}, y] \cong \mathbb{Z}_p[y][x_0, \ldots, x_{k-2}]`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
integer polynomial in `\mathbb{Z}_p[x0, \ldots, x{k-2}, y]`
|
|
p : Integer
|
|
modulus of `f`
|
|
|
|
Returns
|
|
=======
|
|
|
|
contf : PolyElement
|
|
integer polynomial in `\mathbb{Z}_p[y]`, content of `f`
|
|
ppf : PolyElement
|
|
primitive part of `f`, i.e. `\frac{f}{contf}`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import _primitive
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x, y = ring("x, y", ZZ)
|
|
>>> p = 3
|
|
|
|
>>> f = x**2*y**2 + x**2*y - y**2 - y
|
|
>>> _primitive(f, p)
|
|
(y**2 + y, x**2 - 1)
|
|
|
|
>>> R, x, y, z = ring("x, y, z", ZZ)
|
|
|
|
>>> f = x*y*z - y**2*z**2
|
|
>>> _primitive(f, p)
|
|
(z, x*y - y**2*z)
|
|
|
|
"""
|
|
ring = f.ring
|
|
dom = ring.domain
|
|
k = ring.ngens
|
|
|
|
coeffs = {}
|
|
for monom, coeff in f.iterterms():
|
|
if monom[:-1] not in coeffs:
|
|
coeffs[monom[:-1]] = {}
|
|
coeffs[monom[:-1]][monom[-1]] = coeff
|
|
|
|
cont = []
|
|
for coeff in iter(coeffs.values()):
|
|
cont = gf_gcd(cont, gf_from_dict(coeff, p, dom), p, dom)
|
|
|
|
yring = ring.clone(symbols=ring.symbols[k-1])
|
|
contf = yring.from_dense(cont).trunc_ground(p)
|
|
|
|
return contf, f.quo(contf.set_ring(ring))
|
|
|
|
|
|
def _deg(f):
|
|
r"""
|
|
Compute the degree of a multivariate polynomial
|
|
`f \in K[x_0, \ldots, x_{k-2}, y] \cong K[y][x_0, \ldots, x_{k-2}]`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
polynomial in `K[x_0, \ldots, x_{k-2}, y]`
|
|
|
|
Returns
|
|
=======
|
|
|
|
degf : Integer tuple
|
|
degree of `f` in `x_0, \ldots, x_{k-2}`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import _deg
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x, y = ring("x, y", ZZ)
|
|
|
|
>>> f = x**2*y**2 + x**2*y - 1
|
|
>>> _deg(f)
|
|
(2,)
|
|
|
|
>>> R, x, y, z = ring("x, y, z", ZZ)
|
|
|
|
>>> f = x**2*y**2 + x**2*y - 1
|
|
>>> _deg(f)
|
|
(2, 2)
|
|
|
|
>>> f = x*y*z - y**2*z**2
|
|
>>> _deg(f)
|
|
(1, 1)
|
|
|
|
"""
|
|
k = f.ring.ngens
|
|
degf = (0,) * (k-1)
|
|
for monom in f.itermonoms():
|
|
if monom[:-1] > degf:
|
|
degf = monom[:-1]
|
|
return degf
|
|
|
|
|
|
def _LC(f):
|
|
r"""
|
|
Compute the leading coefficient of a multivariate polynomial
|
|
`f \in K[x_0, \ldots, x_{k-2}, y] \cong K[y][x_0, \ldots, x_{k-2}]`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
polynomial in `K[x_0, \ldots, x_{k-2}, y]`
|
|
|
|
Returns
|
|
=======
|
|
|
|
lcf : PolyElement
|
|
polynomial in `K[y]`, leading coefficient of `f`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import _LC
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x, y = ring("x, y", ZZ)
|
|
|
|
>>> f = x**2*y**2 + x**2*y - 1
|
|
>>> _LC(f)
|
|
y**2 + y
|
|
|
|
>>> R, x, y, z = ring("x, y, z", ZZ)
|
|
|
|
>>> f = x**2*y**2 + x**2*y - 1
|
|
>>> _LC(f)
|
|
1
|
|
|
|
>>> f = x*y*z - y**2*z**2
|
|
>>> _LC(f)
|
|
z
|
|
|
|
"""
|
|
ring = f.ring
|
|
k = ring.ngens
|
|
yring = ring.clone(symbols=ring.symbols[k-1])
|
|
y = yring.gens[0]
|
|
degf = _deg(f)
|
|
|
|
lcf = yring.zero
|
|
for monom, coeff in f.iterterms():
|
|
if monom[:-1] == degf:
|
|
lcf += coeff*y**monom[-1]
|
|
return lcf
|
|
|
|
|
|
def _swap(f, i):
|
|
"""
|
|
Make the variable `x_i` the leading one in a multivariate polynomial `f`.
|
|
"""
|
|
ring = f.ring
|
|
fswap = ring.zero
|
|
for monom, coeff in f.iterterms():
|
|
monomswap = (monom[i],) + monom[:i] + monom[i+1:]
|
|
fswap[monomswap] = coeff
|
|
return fswap
|
|
|
|
|
|
def _degree_bound_bivariate(f, g):
|
|
r"""
|
|
Compute upper degree bounds for the GCD of two bivariate
|
|
integer polynomials `f` and `g`.
|
|
|
|
The GCD is viewed as a polynomial in `\mathbb{Z}[y][x]` and the
|
|
function returns an upper bound for its degree and one for the degree
|
|
of its content. This is done by choosing a suitable prime `p` and
|
|
computing the GCD of the contents of `f \; \mathrm{mod} \, p` and
|
|
`g \; \mathrm{mod} \, p`. The choice of `p` guarantees that the degree
|
|
of the content in `\mathbb{Z}_p[y]` is greater than or equal to the
|
|
degree in `\mathbb{Z}[y]`. To obtain the degree bound in the variable
|
|
`x`, the polynomials are evaluated at `y = a` for a suitable
|
|
`a \in \mathbb{Z}_p` and then their GCD in `\mathbb{Z}_p[x]` is
|
|
computed. If no such `a` exists, i.e. the degree in `\mathbb{Z}_p[x]`
|
|
is always smaller than the one in `\mathbb{Z}[y][x]`, then the bound is
|
|
set to the minimum of the degrees of `f` and `g` in `x`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
bivariate integer polynomial
|
|
g : PolyElement
|
|
bivariate integer polynomial
|
|
|
|
Returns
|
|
=======
|
|
|
|
xbound : Integer
|
|
upper bound for the degree of the GCD of the polynomials `f` and
|
|
`g` in the variable `x`
|
|
ycontbound : Integer
|
|
upper bound for the degree of the content of the GCD of the
|
|
polynomials `f` and `g` in the variable `y`
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Monagan00]_
|
|
|
|
"""
|
|
ring = f.ring
|
|
|
|
gamma1 = ring.domain.gcd(f.LC, g.LC)
|
|
gamma2 = ring.domain.gcd(_swap(f, 1).LC, _swap(g, 1).LC)
|
|
badprimes = gamma1 * gamma2
|
|
p = 1
|
|
|
|
p = nextprime(p)
|
|
while badprimes % p == 0:
|
|
p = nextprime(p)
|
|
|
|
fp = f.trunc_ground(p)
|
|
gp = g.trunc_ground(p)
|
|
contfp, fp = _primitive(fp, p)
|
|
contgp, gp = _primitive(gp, p)
|
|
conthp = _gf_gcd(contfp, contgp, p) # polynomial in Z_p[y]
|
|
ycontbound = conthp.degree()
|
|
|
|
# polynomial in Z_p[y]
|
|
delta = _gf_gcd(_LC(fp), _LC(gp), p)
|
|
|
|
for a in range(p):
|
|
if not delta.evaluate(0, a) % p:
|
|
continue
|
|
fpa = fp.evaluate(1, a).trunc_ground(p)
|
|
gpa = gp.evaluate(1, a).trunc_ground(p)
|
|
hpa = _gf_gcd(fpa, gpa, p)
|
|
xbound = hpa.degree()
|
|
return xbound, ycontbound
|
|
|
|
return min(fp.degree(), gp.degree()), ycontbound
|
|
|
|
|
|
def _chinese_remainder_reconstruction_multivariate(hp, hq, p, q):
|
|
r"""
|
|
Construct a polynomial `h_{pq}` in
|
|
`\mathbb{Z}_{p q}[x_0, \ldots, x_{k-1}]` such that
|
|
|
|
.. math ::
|
|
|
|
h_{pq} = h_p \; \mathrm{mod} \, p
|
|
|
|
h_{pq} = h_q \; \mathrm{mod} \, q
|
|
|
|
for relatively prime integers `p` and `q` and polynomials
|
|
`h_p` and `h_q` in `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]` and
|
|
`\mathbb{Z}_q[x_0, \ldots, x_{k-1}]` respectively.
|
|
|
|
The coefficients of the polynomial `h_{pq}` are computed with the
|
|
Chinese Remainder Theorem. The symmetric representation in
|
|
`\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`,
|
|
`\mathbb{Z}_q[x_0, \ldots, x_{k-1}]` and
|
|
`\mathbb{Z}_{p q}[x_0, \ldots, x_{k-1}]` is used.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
hp : PolyElement
|
|
multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
|
|
hq : PolyElement
|
|
multivariate integer polynomial with coefficients in `\mathbb{Z}_q`
|
|
p : Integer
|
|
modulus of `h_p`, relatively prime to `q`
|
|
q : Integer
|
|
modulus of `h_q`, relatively prime to `p`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import _chinese_remainder_reconstruction_multivariate
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x, y = ring("x, y", ZZ)
|
|
>>> p = 3
|
|
>>> q = 5
|
|
|
|
>>> hp = x**3*y - x**2 - 1
|
|
>>> hq = -x**3*y - 2*x*y**2 + 2
|
|
|
|
>>> hpq = _chinese_remainder_reconstruction_multivariate(hp, hq, p, q)
|
|
>>> hpq
|
|
4*x**3*y + 5*x**2 + 3*x*y**2 + 2
|
|
|
|
>>> hpq.trunc_ground(p) == hp
|
|
True
|
|
>>> hpq.trunc_ground(q) == hq
|
|
True
|
|
|
|
>>> R, x, y, z = ring("x, y, z", ZZ)
|
|
>>> p = 6
|
|
>>> q = 5
|
|
|
|
>>> hp = 3*x**4 - y**3*z + z
|
|
>>> hq = -2*x**4 + z
|
|
|
|
>>> hpq = _chinese_remainder_reconstruction_multivariate(hp, hq, p, q)
|
|
>>> hpq
|
|
3*x**4 + 5*y**3*z + z
|
|
|
|
>>> hpq.trunc_ground(p) == hp
|
|
True
|
|
>>> hpq.trunc_ground(q) == hq
|
|
True
|
|
|
|
"""
|
|
hpmonoms = set(hp.monoms())
|
|
hqmonoms = set(hq.monoms())
|
|
monoms = hpmonoms.intersection(hqmonoms)
|
|
hpmonoms.difference_update(monoms)
|
|
hqmonoms.difference_update(monoms)
|
|
|
|
zero = hp.ring.domain.zero
|
|
|
|
hpq = hp.ring.zero
|
|
|
|
if isinstance(hp.ring.domain, PolynomialRing):
|
|
crt_ = _chinese_remainder_reconstruction_multivariate
|
|
else:
|
|
def crt_(cp, cq, p, q):
|
|
return crt([p, q], [cp, cq], symmetric=True)[0]
|
|
|
|
for monom in monoms:
|
|
hpq[monom] = crt_(hp[monom], hq[monom], p, q)
|
|
for monom in hpmonoms:
|
|
hpq[monom] = crt_(hp[monom], zero, p, q)
|
|
for monom in hqmonoms:
|
|
hpq[monom] = crt_(zero, hq[monom], p, q)
|
|
|
|
return hpq
|
|
|
|
|
|
def _interpolate_multivariate(evalpoints, hpeval, ring, i, p, ground=False):
|
|
r"""
|
|
Reconstruct a polynomial `h_p` in `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`
|
|
from a list of evaluation points in `\mathbb{Z}_p` and a list of
|
|
polynomials in
|
|
`\mathbb{Z}_p[x_0, \ldots, x_{i-1}, x_{i+1}, \ldots, x_{k-1}]`, which
|
|
are the images of `h_p` evaluated in the variable `x_i`.
|
|
|
|
It is also possible to reconstruct a parameter of the ground domain,
|
|
i.e. if `h_p` is a polynomial over `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`.
|
|
In this case, one has to set ``ground=True``.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
evalpoints : list of Integer objects
|
|
list of evaluation points in `\mathbb{Z}_p`
|
|
hpeval : list of PolyElement objects
|
|
list of polynomials in (resp. over)
|
|
`\mathbb{Z}_p[x_0, \ldots, x_{i-1}, x_{i+1}, \ldots, x_{k-1}]`,
|
|
images of `h_p` evaluated in the variable `x_i`
|
|
ring : PolyRing
|
|
`h_p` will be an element of this ring
|
|
i : Integer
|
|
index of the variable which has to be reconstructed
|
|
p : Integer
|
|
prime number, modulus of `h_p`
|
|
ground : Boolean
|
|
indicates whether `x_i` is in the ground domain, default is
|
|
``False``
|
|
|
|
Returns
|
|
=======
|
|
|
|
hp : PolyElement
|
|
interpolated polynomial in (resp. over)
|
|
`\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`
|
|
|
|
"""
|
|
hp = ring.zero
|
|
|
|
if ground:
|
|
domain = ring.domain.domain
|
|
y = ring.domain.gens[i]
|
|
else:
|
|
domain = ring.domain
|
|
y = ring.gens[i]
|
|
|
|
for a, hpa in zip(evalpoints, hpeval):
|
|
numer = ring.one
|
|
denom = domain.one
|
|
for b in evalpoints:
|
|
if b == a:
|
|
continue
|
|
|
|
numer *= y - b
|
|
denom *= a - b
|
|
|
|
denom = domain.invert(denom, p)
|
|
coeff = numer.mul_ground(denom)
|
|
hp += hpa.set_ring(ring) * coeff
|
|
|
|
return hp.trunc_ground(p)
|
|
|
|
|
|
def modgcd_bivariate(f, g):
|
|
r"""
|
|
Computes the GCD of two polynomials in `\mathbb{Z}[x, y]` using a
|
|
modular algorithm.
|
|
|
|
The algorithm computes the GCD of two bivariate integer polynomials
|
|
`f` and `g` by calculating the GCD in `\mathbb{Z}_p[x, y]` for
|
|
suitable primes `p` and then reconstructing the coefficients with the
|
|
Chinese Remainder Theorem. To compute the bivariate GCD over
|
|
`\mathbb{Z}_p`, the polynomials `f \; \mathrm{mod} \, p` and
|
|
`g \; \mathrm{mod} \, p` are evaluated at `y = a` for certain
|
|
`a \in \mathbb{Z}_p` and then their univariate GCD in `\mathbb{Z}_p[x]`
|
|
is computed. Interpolating those yields the bivariate GCD in
|
|
`\mathbb{Z}_p[x, y]`. To verify the result in `\mathbb{Z}[x, y]`, trial
|
|
division is done, but only for candidates which are very likely the
|
|
desired GCD.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
bivariate integer polynomial
|
|
g : PolyElement
|
|
bivariate integer polynomial
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
GCD of the polynomials `f` and `g`
|
|
cff : PolyElement
|
|
cofactor of `f`, i.e. `\frac{f}{h}`
|
|
cfg : PolyElement
|
|
cofactor of `g`, i.e. `\frac{g}{h}`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import modgcd_bivariate
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x, y = ring("x, y", ZZ)
|
|
|
|
>>> f = x**2 - y**2
|
|
>>> g = x**2 + 2*x*y + y**2
|
|
|
|
>>> h, cff, cfg = modgcd_bivariate(f, g)
|
|
>>> h, cff, cfg
|
|
(x + y, x - y, x + y)
|
|
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
>>> f = x**2*y - x**2 - 4*y + 4
|
|
>>> g = x + 2
|
|
|
|
>>> h, cff, cfg = modgcd_bivariate(f, g)
|
|
>>> h, cff, cfg
|
|
(x + 2, x*y - x - 2*y + 2, 1)
|
|
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Monagan00]_
|
|
|
|
"""
|
|
assert f.ring == g.ring and f.ring.domain.is_ZZ
|
|
|
|
result = _trivial_gcd(f, g)
|
|
if result is not None:
|
|
return result
|
|
|
|
ring = f.ring
|
|
|
|
cf, f = f.primitive()
|
|
cg, g = g.primitive()
|
|
ch = ring.domain.gcd(cf, cg)
|
|
|
|
xbound, ycontbound = _degree_bound_bivariate(f, g)
|
|
if xbound == ycontbound == 0:
|
|
return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
|
|
|
|
fswap = _swap(f, 1)
|
|
gswap = _swap(g, 1)
|
|
degyf = fswap.degree()
|
|
degyg = gswap.degree()
|
|
|
|
ybound, xcontbound = _degree_bound_bivariate(fswap, gswap)
|
|
if ybound == xcontbound == 0:
|
|
return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
|
|
|
|
# TODO: to improve performance, choose the main variable here
|
|
|
|
gamma1 = ring.domain.gcd(f.LC, g.LC)
|
|
gamma2 = ring.domain.gcd(fswap.LC, gswap.LC)
|
|
badprimes = gamma1 * gamma2
|
|
m = 1
|
|
p = 1
|
|
|
|
while True:
|
|
p = nextprime(p)
|
|
while badprimes % p == 0:
|
|
p = nextprime(p)
|
|
|
|
fp = f.trunc_ground(p)
|
|
gp = g.trunc_ground(p)
|
|
contfp, fp = _primitive(fp, p)
|
|
contgp, gp = _primitive(gp, p)
|
|
conthp = _gf_gcd(contfp, contgp, p) # monic polynomial in Z_p[y]
|
|
degconthp = conthp.degree()
|
|
|
|
if degconthp > ycontbound:
|
|
continue
|
|
elif degconthp < ycontbound:
|
|
m = 1
|
|
ycontbound = degconthp
|
|
continue
|
|
|
|
# polynomial in Z_p[y]
|
|
delta = _gf_gcd(_LC(fp), _LC(gp), p)
|
|
|
|
degcontfp = contfp.degree()
|
|
degcontgp = contgp.degree()
|
|
degdelta = delta.degree()
|
|
|
|
N = min(degyf - degcontfp, degyg - degcontgp,
|
|
ybound - ycontbound + degdelta) + 1
|
|
|
|
if p < N:
|
|
continue
|
|
|
|
n = 0
|
|
evalpoints = []
|
|
hpeval = []
|
|
unlucky = False
|
|
|
|
for a in range(p):
|
|
deltaa = delta.evaluate(0, a)
|
|
if not deltaa % p:
|
|
continue
|
|
|
|
fpa = fp.evaluate(1, a).trunc_ground(p)
|
|
gpa = gp.evaluate(1, a).trunc_ground(p)
|
|
hpa = _gf_gcd(fpa, gpa, p) # monic polynomial in Z_p[x]
|
|
deghpa = hpa.degree()
|
|
|
|
if deghpa > xbound:
|
|
continue
|
|
elif deghpa < xbound:
|
|
m = 1
|
|
xbound = deghpa
|
|
unlucky = True
|
|
break
|
|
|
|
hpa = hpa.mul_ground(deltaa).trunc_ground(p)
|
|
evalpoints.append(a)
|
|
hpeval.append(hpa)
|
|
n += 1
|
|
|
|
if n == N:
|
|
break
|
|
|
|
if unlucky:
|
|
continue
|
|
if n < N:
|
|
continue
|
|
|
|
hp = _interpolate_multivariate(evalpoints, hpeval, ring, 1, p)
|
|
|
|
hp = _primitive(hp, p)[1]
|
|
hp = hp * conthp.set_ring(ring)
|
|
degyhp = hp.degree(1)
|
|
|
|
if degyhp > ybound:
|
|
continue
|
|
if degyhp < ybound:
|
|
m = 1
|
|
ybound = degyhp
|
|
continue
|
|
|
|
hp = hp.mul_ground(gamma1).trunc_ground(p)
|
|
if m == 1:
|
|
m = p
|
|
hlastm = hp
|
|
continue
|
|
|
|
hm = _chinese_remainder_reconstruction_multivariate(hp, hlastm, p, m)
|
|
m *= p
|
|
|
|
if not hm == hlastm:
|
|
hlastm = hm
|
|
continue
|
|
|
|
h = hm.quo_ground(hm.content())
|
|
fquo, frem = f.div(h)
|
|
gquo, grem = g.div(h)
|
|
if not frem and not grem:
|
|
if h.LC < 0:
|
|
ch = -ch
|
|
h = h.mul_ground(ch)
|
|
cff = fquo.mul_ground(cf // ch)
|
|
cfg = gquo.mul_ground(cg // ch)
|
|
return h, cff, cfg
|
|
|
|
|
|
def _modgcd_multivariate_p(f, g, p, degbound, contbound):
|
|
r"""
|
|
Compute the GCD of two polynomials in
|
|
`\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`.
|
|
|
|
The algorithm reduces the problem step by step by evaluating the
|
|
polynomials `f` and `g` at `x_{k-1} = a` for suitable
|
|
`a \in \mathbb{Z}_p` and then calls itself recursively to compute the GCD
|
|
in `\mathbb{Z}_p[x_0, \ldots, x_{k-2}]`. If these recursive calls are
|
|
successful for enough evaluation points, the GCD in `k` variables is
|
|
interpolated, otherwise the algorithm returns ``None``. Every time a GCD
|
|
or a content is computed, their degrees are compared with the bounds. If
|
|
a degree greater then the bound is encountered, then the current call
|
|
returns ``None`` and a new evaluation point has to be chosen. If at some
|
|
point the degree is smaller, the correspondent bound is updated and the
|
|
algorithm fails.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
|
|
g : PolyElement
|
|
multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
|
|
p : Integer
|
|
prime number, modulus of `f` and `g`
|
|
degbound : list of Integer objects
|
|
``degbound[i]`` is an upper bound for the degree of the GCD of `f`
|
|
and `g` in the variable `x_i`
|
|
contbound : list of Integer objects
|
|
``contbound[i]`` is an upper bound for the degree of the content of
|
|
the GCD in `\mathbb{Z}_p[x_i][x_0, \ldots, x_{i-1}]`,
|
|
``contbound[0]`` is not used can therefore be chosen
|
|
arbitrarily.
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
GCD of the polynomials `f` and `g` or ``None``
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Monagan00]_
|
|
2. [Brown71]_
|
|
|
|
"""
|
|
ring = f.ring
|
|
k = ring.ngens
|
|
|
|
if k == 1:
|
|
h = _gf_gcd(f, g, p).trunc_ground(p)
|
|
degh = h.degree()
|
|
|
|
if degh > degbound[0]:
|
|
return None
|
|
if degh < degbound[0]:
|
|
degbound[0] = degh
|
|
raise ModularGCDFailed
|
|
|
|
return h
|
|
|
|
degyf = f.degree(k-1)
|
|
degyg = g.degree(k-1)
|
|
|
|
contf, f = _primitive(f, p)
|
|
contg, g = _primitive(g, p)
|
|
|
|
conth = _gf_gcd(contf, contg, p) # polynomial in Z_p[y]
|
|
|
|
degcontf = contf.degree()
|
|
degcontg = contg.degree()
|
|
degconth = conth.degree()
|
|
|
|
if degconth > contbound[k-1]:
|
|
return None
|
|
if degconth < contbound[k-1]:
|
|
contbound[k-1] = degconth
|
|
raise ModularGCDFailed
|
|
|
|
lcf = _LC(f)
|
|
lcg = _LC(g)
|
|
|
|
delta = _gf_gcd(lcf, lcg, p) # polynomial in Z_p[y]
|
|
|
|
evaltest = delta
|
|
|
|
for i in range(k-1):
|
|
evaltest *= _gf_gcd(_LC(_swap(f, i)), _LC(_swap(g, i)), p)
|
|
|
|
degdelta = delta.degree()
|
|
|
|
N = min(degyf - degcontf, degyg - degcontg,
|
|
degbound[k-1] - contbound[k-1] + degdelta) + 1
|
|
|
|
if p < N:
|
|
return None
|
|
|
|
n = 0
|
|
d = 0
|
|
evalpoints = []
|
|
heval = []
|
|
points = list(range(p))
|
|
|
|
while points:
|
|
a = random.sample(points, 1)[0]
|
|
points.remove(a)
|
|
|
|
if not evaltest.evaluate(0, a) % p:
|
|
continue
|
|
|
|
deltaa = delta.evaluate(0, a) % p
|
|
|
|
fa = f.evaluate(k-1, a).trunc_ground(p)
|
|
ga = g.evaluate(k-1, a).trunc_ground(p)
|
|
|
|
# polynomials in Z_p[x_0, ..., x_{k-2}]
|
|
ha = _modgcd_multivariate_p(fa, ga, p, degbound, contbound)
|
|
|
|
if ha is None:
|
|
d += 1
|
|
if d > n:
|
|
return None
|
|
continue
|
|
|
|
if ha.is_ground:
|
|
h = conth.set_ring(ring).trunc_ground(p)
|
|
return h
|
|
|
|
ha = ha.mul_ground(deltaa).trunc_ground(p)
|
|
|
|
evalpoints.append(a)
|
|
heval.append(ha)
|
|
n += 1
|
|
|
|
if n == N:
|
|
h = _interpolate_multivariate(evalpoints, heval, ring, k-1, p)
|
|
|
|
h = _primitive(h, p)[1] * conth.set_ring(ring)
|
|
degyh = h.degree(k-1)
|
|
|
|
if degyh > degbound[k-1]:
|
|
return None
|
|
if degyh < degbound[k-1]:
|
|
degbound[k-1] = degyh
|
|
raise ModularGCDFailed
|
|
|
|
return h
|
|
|
|
return None
|
|
|
|
|
|
def modgcd_multivariate(f, g):
|
|
r"""
|
|
Compute the GCD of two polynomials in `\mathbb{Z}[x_0, \ldots, x_{k-1}]`
|
|
using a modular algorithm.
|
|
|
|
The algorithm computes the GCD of two multivariate integer polynomials
|
|
`f` and `g` by calculating the GCD in
|
|
`\mathbb{Z}_p[x_0, \ldots, x_{k-1}]` for suitable primes `p` and then
|
|
reconstructing the coefficients with the Chinese Remainder Theorem. To
|
|
compute the multivariate GCD over `\mathbb{Z}_p` the recursive
|
|
subroutine :func:`_modgcd_multivariate_p` is used. To verify the result in
|
|
`\mathbb{Z}[x_0, \ldots, x_{k-1}]`, trial division is done, but only for
|
|
candidates which are very likely the desired GCD.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
multivariate integer polynomial
|
|
g : PolyElement
|
|
multivariate integer polynomial
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
GCD of the polynomials `f` and `g`
|
|
cff : PolyElement
|
|
cofactor of `f`, i.e. `\frac{f}{h}`
|
|
cfg : PolyElement
|
|
cofactor of `g`, i.e. `\frac{g}{h}`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import modgcd_multivariate
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x, y = ring("x, y", ZZ)
|
|
|
|
>>> f = x**2 - y**2
|
|
>>> g = x**2 + 2*x*y + y**2
|
|
|
|
>>> h, cff, cfg = modgcd_multivariate(f, g)
|
|
>>> h, cff, cfg
|
|
(x + y, x - y, x + y)
|
|
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
>>> R, x, y, z = ring("x, y, z", ZZ)
|
|
|
|
>>> f = x*z**2 - y*z**2
|
|
>>> g = x**2*z + z
|
|
|
|
>>> h, cff, cfg = modgcd_multivariate(f, g)
|
|
>>> h, cff, cfg
|
|
(z, x*z - y*z, x**2 + 1)
|
|
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Monagan00]_
|
|
2. [Brown71]_
|
|
|
|
See also
|
|
========
|
|
|
|
_modgcd_multivariate_p
|
|
|
|
"""
|
|
assert f.ring == g.ring and f.ring.domain.is_ZZ
|
|
|
|
result = _trivial_gcd(f, g)
|
|
if result is not None:
|
|
return result
|
|
|
|
ring = f.ring
|
|
k = ring.ngens
|
|
|
|
# divide out integer content
|
|
cf, f = f.primitive()
|
|
cg, g = g.primitive()
|
|
ch = ring.domain.gcd(cf, cg)
|
|
|
|
gamma = ring.domain.gcd(f.LC, g.LC)
|
|
|
|
badprimes = ring.domain.one
|
|
for i in range(k):
|
|
badprimes *= ring.domain.gcd(_swap(f, i).LC, _swap(g, i).LC)
|
|
|
|
degbound = [min(fdeg, gdeg) for fdeg, gdeg in zip(f.degrees(), g.degrees())]
|
|
contbound = list(degbound)
|
|
|
|
m = 1
|
|
p = 1
|
|
|
|
while True:
|
|
p = nextprime(p)
|
|
while badprimes % p == 0:
|
|
p = nextprime(p)
|
|
|
|
fp = f.trunc_ground(p)
|
|
gp = g.trunc_ground(p)
|
|
|
|
try:
|
|
# monic GCD of fp, gp in Z_p[x_0, ..., x_{k-2}, y]
|
|
hp = _modgcd_multivariate_p(fp, gp, p, degbound, contbound)
|
|
except ModularGCDFailed:
|
|
m = 1
|
|
continue
|
|
|
|
if hp is None:
|
|
continue
|
|
|
|
hp = hp.mul_ground(gamma).trunc_ground(p)
|
|
if m == 1:
|
|
m = p
|
|
hlastm = hp
|
|
continue
|
|
|
|
hm = _chinese_remainder_reconstruction_multivariate(hp, hlastm, p, m)
|
|
m *= p
|
|
|
|
if not hm == hlastm:
|
|
hlastm = hm
|
|
continue
|
|
|
|
h = hm.primitive()[1]
|
|
fquo, frem = f.div(h)
|
|
gquo, grem = g.div(h)
|
|
if not frem and not grem:
|
|
if h.LC < 0:
|
|
ch = -ch
|
|
h = h.mul_ground(ch)
|
|
cff = fquo.mul_ground(cf // ch)
|
|
cfg = gquo.mul_ground(cg // ch)
|
|
return h, cff, cfg
|
|
|
|
|
|
def _gf_div(f, g, p):
|
|
r"""
|
|
Compute `\frac f g` modulo `p` for two univariate polynomials over
|
|
`\mathbb Z_p`.
|
|
"""
|
|
ring = f.ring
|
|
densequo, denserem = gf_div(f.to_dense(), g.to_dense(), p, ring.domain)
|
|
return ring.from_dense(densequo), ring.from_dense(denserem)
|
|
|
|
|
|
def _rational_function_reconstruction(c, p, m):
|
|
r"""
|
|
Reconstruct a rational function `\frac a b` in `\mathbb Z_p(t)` from
|
|
|
|
.. math::
|
|
|
|
c = \frac a b \; \mathrm{mod} \, m,
|
|
|
|
where `c` and `m` are polynomials in `\mathbb Z_p[t]` and `m` has
|
|
positive degree.
|
|
|
|
The algorithm is based on the Euclidean Algorithm. In general, `m` is
|
|
not irreducible, so it is possible that `b` is not invertible modulo
|
|
`m`. In that case ``None`` is returned.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
c : PolyElement
|
|
univariate polynomial in `\mathbb Z[t]`
|
|
p : Integer
|
|
prime number
|
|
m : PolyElement
|
|
modulus, not necessarily irreducible
|
|
|
|
Returns
|
|
=======
|
|
|
|
frac : FracElement
|
|
either `\frac a b` in `\mathbb Z(t)` or ``None``
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Hoeij04]_
|
|
|
|
"""
|
|
ring = c.ring
|
|
domain = ring.domain
|
|
M = m.degree()
|
|
N = M // 2
|
|
D = M - N - 1
|
|
|
|
r0, s0 = m, ring.zero
|
|
r1, s1 = c, ring.one
|
|
|
|
while r1.degree() > N:
|
|
quo = _gf_div(r0, r1, p)[0]
|
|
r0, r1 = r1, (r0 - quo*r1).trunc_ground(p)
|
|
s0, s1 = s1, (s0 - quo*s1).trunc_ground(p)
|
|
|
|
a, b = r1, s1
|
|
if b.degree() > D or _gf_gcd(b, m, p) != 1:
|
|
return None
|
|
|
|
lc = b.LC
|
|
if lc != 1:
|
|
lcinv = domain.invert(lc, p)
|
|
a = a.mul_ground(lcinv).trunc_ground(p)
|
|
b = b.mul_ground(lcinv).trunc_ground(p)
|
|
|
|
field = ring.to_field()
|
|
|
|
return field(a) / field(b)
|
|
|
|
|
|
def _rational_reconstruction_func_coeffs(hm, p, m, ring, k):
|
|
r"""
|
|
Reconstruct every coefficient `c_h` of a polynomial `h` in
|
|
`\mathbb Z_p(t_k)[t_1, \ldots, t_{k-1}][x, z]` from the corresponding
|
|
coefficient `c_{h_m}` of a polynomial `h_m` in
|
|
`\mathbb Z_p[t_1, \ldots, t_k][x, z] \cong \mathbb Z_p[t_k][t_1, \ldots, t_{k-1}][x, z]`
|
|
such that
|
|
|
|
.. math::
|
|
|
|
c_{h_m} = c_h \; \mathrm{mod} \, m,
|
|
|
|
where `m \in \mathbb Z_p[t]`.
|
|
|
|
The reconstruction is based on the Euclidean Algorithm. In general, `m`
|
|
is not irreducible, so it is possible that this fails for some
|
|
coefficient. In that case ``None`` is returned.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
hm : PolyElement
|
|
polynomial in `\mathbb Z[t_1, \ldots, t_k][x, z]`
|
|
p : Integer
|
|
prime number, modulus of `\mathbb Z_p`
|
|
m : PolyElement
|
|
modulus, polynomial in `\mathbb Z[t]`, not necessarily irreducible
|
|
ring : PolyRing
|
|
`\mathbb Z(t_k)[t_1, \ldots, t_{k-1}][x, z]`, `h` will be an
|
|
element of this ring
|
|
k : Integer
|
|
index of the parameter `t_k` which will be reconstructed
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
reconstructed polynomial in
|
|
`\mathbb Z(t_k)[t_1, \ldots, t_{k-1}][x, z]` or ``None``
|
|
|
|
See also
|
|
========
|
|
|
|
_rational_function_reconstruction
|
|
|
|
"""
|
|
h = ring.zero
|
|
|
|
for monom, coeff in hm.iterterms():
|
|
if k == 0:
|
|
coeffh = _rational_function_reconstruction(coeff, p, m)
|
|
|
|
if not coeffh:
|
|
return None
|
|
|
|
else:
|
|
coeffh = ring.domain.zero
|
|
for mon, c in coeff.drop_to_ground(k).iterterms():
|
|
ch = _rational_function_reconstruction(c, p, m)
|
|
|
|
if not ch:
|
|
return None
|
|
|
|
coeffh[mon] = ch
|
|
|
|
h[monom] = coeffh
|
|
|
|
return h
|
|
|
|
|
|
def _gf_gcdex(f, g, p):
|
|
r"""
|
|
Extended Euclidean Algorithm for two univariate polynomials over
|
|
`\mathbb Z_p`.
|
|
|
|
Returns polynomials `s, t` and `h`, such that `h` is the GCD of `f` and
|
|
`g` and `sf + tg = h \; \mathrm{mod} \, p`.
|
|
|
|
"""
|
|
ring = f.ring
|
|
s, t, h = gf_gcdex(f.to_dense(), g.to_dense(), p, ring.domain)
|
|
return ring.from_dense(s), ring.from_dense(t), ring.from_dense(h)
|
|
|
|
|
|
def _trunc(f, minpoly, p):
|
|
r"""
|
|
Compute the reduced representation of a polynomial `f` in
|
|
`\mathbb Z_p[z] / (\check m_{\alpha}(z))[x]`
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
polynomial in `\mathbb Z[x, z]`
|
|
minpoly : PolyElement
|
|
polynomial `\check m_{\alpha} \in \mathbb Z[z]`, not necessarily
|
|
irreducible
|
|
p : Integer
|
|
prime number, modulus of `\mathbb Z_p`
|
|
|
|
Returns
|
|
=======
|
|
|
|
ftrunc : PolyElement
|
|
polynomial in `\mathbb Z[x, z]`, reduced modulo
|
|
`\check m_{\alpha}(z)` and `p`
|
|
|
|
"""
|
|
ring = f.ring
|
|
minpoly = minpoly.set_ring(ring)
|
|
p_ = ring.ground_new(p)
|
|
|
|
return f.trunc_ground(p).rem([minpoly, p_]).trunc_ground(p)
|
|
|
|
|
|
def _euclidean_algorithm(f, g, minpoly, p):
|
|
r"""
|
|
Compute the monic GCD of two univariate polynomials in
|
|
`\mathbb{Z}_p[z]/(\check m_{\alpha}(z))[x]` with the Euclidean
|
|
Algorithm.
|
|
|
|
In general, `\check m_{\alpha}(z)` is not irreducible, so it is possible
|
|
that some leading coefficient is not invertible modulo
|
|
`\check m_{\alpha}(z)`. In that case ``None`` is returned.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f, g : PolyElement
|
|
polynomials in `\mathbb Z[x, z]`
|
|
minpoly : PolyElement
|
|
polynomial in `\mathbb Z[z]`, not necessarily irreducible
|
|
p : Integer
|
|
prime number, modulus of `\mathbb Z_p`
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
GCD of `f` and `g` in `\mathbb Z[z, x]` or ``None``, coefficients
|
|
are in `\left[ -\frac{p-1} 2, \frac{p-1} 2 \right]`
|
|
|
|
"""
|
|
ring = f.ring
|
|
|
|
f = _trunc(f, minpoly, p)
|
|
g = _trunc(g, minpoly, p)
|
|
|
|
while g:
|
|
rem = f
|
|
deg = g.degree(0) # degree in x
|
|
lcinv, _, gcd = _gf_gcdex(ring.dmp_LC(g), minpoly, p)
|
|
|
|
if not gcd == 1:
|
|
return None
|
|
|
|
while True:
|
|
degrem = rem.degree(0) # degree in x
|
|
if degrem < deg:
|
|
break
|
|
quo = (lcinv * ring.dmp_LC(rem)).set_ring(ring)
|
|
rem = _trunc(rem - g.mul_monom((degrem - deg, 0))*quo, minpoly, p)
|
|
|
|
f = g
|
|
g = rem
|
|
|
|
lcfinv = _gf_gcdex(ring.dmp_LC(f), minpoly, p)[0].set_ring(ring)
|
|
|
|
return _trunc(f * lcfinv, minpoly, p)
|
|
|
|
|
|
def _trial_division(f, h, minpoly, p=None):
|
|
r"""
|
|
Check if `h` divides `f` in
|
|
`\mathbb K[t_1, \ldots, t_k][z]/(m_{\alpha}(z))`, where `\mathbb K` is
|
|
either `\mathbb Q` or `\mathbb Z_p`.
|
|
|
|
This algorithm is based on pseudo division and does not use any
|
|
fractions. By default `\mathbb K` is `\mathbb Q`, if a prime number `p`
|
|
is given, `\mathbb Z_p` is chosen instead.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f, h : PolyElement
|
|
polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
|
|
minpoly : PolyElement
|
|
polynomial `m_{\alpha}(z)` in `\mathbb Z[t_1, \ldots, t_k][z]`
|
|
p : Integer or None
|
|
if `p` is given, `\mathbb K` is set to `\mathbb Z_p` instead of
|
|
`\mathbb Q`, default is ``None``
|
|
|
|
Returns
|
|
=======
|
|
|
|
rem : PolyElement
|
|
remainder of `\frac f h`
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] [Hoeij02]_
|
|
|
|
"""
|
|
ring = f.ring
|
|
|
|
zxring = ring.clone(symbols=(ring.symbols[1], ring.symbols[0]))
|
|
|
|
minpoly = minpoly.set_ring(ring)
|
|
|
|
rem = f
|
|
|
|
degrem = rem.degree()
|
|
degh = h.degree()
|
|
degm = minpoly.degree(1)
|
|
|
|
lch = _LC(h).set_ring(ring)
|
|
lcm = minpoly.LC
|
|
|
|
while rem and degrem >= degh:
|
|
# polynomial in Z[t_1, ..., t_k][z]
|
|
lcrem = _LC(rem).set_ring(ring)
|
|
rem = rem*lch - h.mul_monom((degrem - degh, 0))*lcrem
|
|
if p:
|
|
rem = rem.trunc_ground(p)
|
|
degrem = rem.degree(1)
|
|
|
|
while rem and degrem >= degm:
|
|
# polynomial in Z[t_1, ..., t_k][x]
|
|
lcrem = _LC(rem.set_ring(zxring)).set_ring(ring)
|
|
rem = rem.mul_ground(lcm) - minpoly.mul_monom((0, degrem - degm))*lcrem
|
|
if p:
|
|
rem = rem.trunc_ground(p)
|
|
degrem = rem.degree(1)
|
|
|
|
degrem = rem.degree()
|
|
|
|
return rem
|
|
|
|
|
|
def _evaluate_ground(f, i, a):
|
|
r"""
|
|
Evaluate a polynomial `f` at `a` in the `i`-th variable of the ground
|
|
domain.
|
|
"""
|
|
ring = f.ring.clone(domain=f.ring.domain.ring.drop(i))
|
|
fa = ring.zero
|
|
|
|
for monom, coeff in f.iterterms():
|
|
fa[monom] = coeff.evaluate(i, a)
|
|
|
|
return fa
|
|
|
|
|
|
def _func_field_modgcd_p(f, g, minpoly, p):
|
|
r"""
|
|
Compute the GCD of two polynomials `f` and `g` in
|
|
`\mathbb Z_p(t_1, \ldots, t_k)[z]/(\check m_\alpha(z))[x]`.
|
|
|
|
The algorithm reduces the problem step by step by evaluating the
|
|
polynomials `f` and `g` at `t_k = a` for suitable `a \in \mathbb Z_p`
|
|
and then calls itself recursively to compute the GCD in
|
|
`\mathbb Z_p(t_1, \ldots, t_{k-1})[z]/(\check m_\alpha(z))[x]`. If these
|
|
recursive calls are successful, the GCD over `k` variables is
|
|
interpolated, otherwise the algorithm returns ``None``. After
|
|
interpolation, Rational Function Reconstruction is used to obtain the
|
|
correct coefficients. If this fails, a new evaluation point has to be
|
|
chosen, otherwise the desired polynomial is obtained by clearing
|
|
denominators. The result is verified with a fraction free trial
|
|
division.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f, g : PolyElement
|
|
polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
|
|
minpoly : PolyElement
|
|
polynomial in `\mathbb Z[t_1, \ldots, t_k][z]`, not necessarily
|
|
irreducible
|
|
p : Integer
|
|
prime number, modulus of `\mathbb Z_p`
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
primitive associate in `\mathbb Z[t_1, \ldots, t_k][x, z]` of the
|
|
GCD of the polynomials `f` and `g` or ``None``, coefficients are
|
|
in `\left[ -\frac{p-1} 2, \frac{p-1} 2 \right]`
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Hoeij04]_
|
|
|
|
"""
|
|
ring = f.ring
|
|
domain = ring.domain # Z[t_1, ..., t_k]
|
|
|
|
if isinstance(domain, PolynomialRing):
|
|
k = domain.ngens
|
|
else:
|
|
return _euclidean_algorithm(f, g, minpoly, p)
|
|
|
|
if k == 1:
|
|
qdomain = domain.ring.to_field()
|
|
else:
|
|
qdomain = domain.ring.drop_to_ground(k - 1)
|
|
qdomain = qdomain.clone(domain=qdomain.domain.ring.to_field())
|
|
|
|
qring = ring.clone(domain=qdomain) # = Z(t_k)[t_1, ..., t_{k-1}][x, z]
|
|
|
|
n = 1
|
|
d = 1
|
|
|
|
# polynomial in Z_p[t_1, ..., t_k][z]
|
|
gamma = ring.dmp_LC(f) * ring.dmp_LC(g)
|
|
# polynomial in Z_p[t_1, ..., t_k]
|
|
delta = minpoly.LC
|
|
|
|
evalpoints = []
|
|
heval = []
|
|
LMlist = []
|
|
points = list(range(p))
|
|
|
|
while points:
|
|
a = random.sample(points, 1)[0]
|
|
points.remove(a)
|
|
|
|
if k == 1:
|
|
test = delta.evaluate(k-1, a) % p == 0
|
|
else:
|
|
test = delta.evaluate(k-1, a).trunc_ground(p) == 0
|
|
|
|
if test:
|
|
continue
|
|
|
|
gammaa = _evaluate_ground(gamma, k-1, a)
|
|
minpolya = _evaluate_ground(minpoly, k-1, a)
|
|
|
|
if gammaa.rem([minpolya, gammaa.ring(p)]) == 0:
|
|
continue
|
|
|
|
fa = _evaluate_ground(f, k-1, a)
|
|
ga = _evaluate_ground(g, k-1, a)
|
|
|
|
# polynomial in Z_p[x, t_1, ..., t_{k-1}, z]/(minpoly)
|
|
ha = _func_field_modgcd_p(fa, ga, minpolya, p)
|
|
|
|
if ha is None:
|
|
d += 1
|
|
if d > n:
|
|
return None
|
|
continue
|
|
|
|
if ha == 1:
|
|
return ha
|
|
|
|
LM = [ha.degree()] + [0]*(k-1)
|
|
if k > 1:
|
|
for monom, coeff in ha.iterterms():
|
|
if monom[0] == LM[0] and coeff.LM > tuple(LM[1:]):
|
|
LM[1:] = coeff.LM
|
|
|
|
evalpoints_a = [a]
|
|
heval_a = [ha]
|
|
if k == 1:
|
|
m = qring.domain.get_ring().one
|
|
else:
|
|
m = qring.domain.domain.get_ring().one
|
|
|
|
t = m.ring.gens[0]
|
|
|
|
for b, hb, LMhb in zip(evalpoints, heval, LMlist):
|
|
if LMhb == LM:
|
|
evalpoints_a.append(b)
|
|
heval_a.append(hb)
|
|
m *= (t - b)
|
|
|
|
m = m.trunc_ground(p)
|
|
evalpoints.append(a)
|
|
heval.append(ha)
|
|
LMlist.append(LM)
|
|
n += 1
|
|
|
|
# polynomial in Z_p[t_1, ..., t_k][x, z]
|
|
h = _interpolate_multivariate(evalpoints_a, heval_a, ring, k-1, p, ground=True)
|
|
|
|
# polynomial in Z_p(t_k)[t_1, ..., t_{k-1}][x, z]
|
|
h = _rational_reconstruction_func_coeffs(h, p, m, qring, k-1)
|
|
|
|
if h is None:
|
|
continue
|
|
|
|
if k == 1:
|
|
dom = qring.domain.field
|
|
den = dom.ring.one
|
|
|
|
for coeff in h.itercoeffs():
|
|
den = dom.ring.from_dense(gf_lcm(den.to_dense(), coeff.denom.to_dense(),
|
|
p, dom.domain))
|
|
|
|
else:
|
|
dom = qring.domain.domain.field
|
|
den = dom.ring.one
|
|
|
|
for coeff in h.itercoeffs():
|
|
for c in coeff.itercoeffs():
|
|
den = dom.ring.from_dense(gf_lcm(den.to_dense(), c.denom.to_dense(),
|
|
p, dom.domain))
|
|
|
|
den = qring.domain_new(den.trunc_ground(p))
|
|
h = ring(h.mul_ground(den).as_expr()).trunc_ground(p)
|
|
|
|
if not _trial_division(f, h, minpoly, p) and not _trial_division(g, h, minpoly, p):
|
|
return h
|
|
|
|
return None
|
|
|
|
|
|
def _integer_rational_reconstruction(c, m, domain):
|
|
r"""
|
|
Reconstruct a rational number `\frac a b` from
|
|
|
|
.. math::
|
|
|
|
c = \frac a b \; \mathrm{mod} \, m,
|
|
|
|
where `c` and `m` are integers.
|
|
|
|
The algorithm is based on the Euclidean Algorithm. In general, `m` is
|
|
not a prime number, so it is possible that `b` is not invertible modulo
|
|
`m`. In that case ``None`` is returned.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
c : Integer
|
|
`c = \frac a b \; \mathrm{mod} \, m`
|
|
m : Integer
|
|
modulus, not necessarily prime
|
|
domain : IntegerRing
|
|
`a, b, c` are elements of ``domain``
|
|
|
|
Returns
|
|
=======
|
|
|
|
frac : Rational
|
|
either `\frac a b` in `\mathbb Q` or ``None``
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Wang81]_
|
|
|
|
"""
|
|
if c < 0:
|
|
c += m
|
|
|
|
r0, s0 = m, domain.zero
|
|
r1, s1 = c, domain.one
|
|
|
|
bound = sqrt(m / 2) # still correct if replaced by ZZ.sqrt(m // 2) ?
|
|
|
|
while r1 >= bound:
|
|
quo = r0 // r1
|
|
r0, r1 = r1, r0 - quo*r1
|
|
s0, s1 = s1, s0 - quo*s1
|
|
|
|
if abs(s1) >= bound:
|
|
return None
|
|
|
|
if s1 < 0:
|
|
a, b = -r1, -s1
|
|
elif s1 > 0:
|
|
a, b = r1, s1
|
|
else:
|
|
return None
|
|
|
|
field = domain.get_field()
|
|
|
|
return field(a) / field(b)
|
|
|
|
|
|
def _rational_reconstruction_int_coeffs(hm, m, ring):
|
|
r"""
|
|
Reconstruct every rational coefficient `c_h` of a polynomial `h` in
|
|
`\mathbb Q[t_1, \ldots, t_k][x, z]` from the corresponding integer
|
|
coefficient `c_{h_m}` of a polynomial `h_m` in
|
|
`\mathbb Z[t_1, \ldots, t_k][x, z]` such that
|
|
|
|
.. math::
|
|
|
|
c_{h_m} = c_h \; \mathrm{mod} \, m,
|
|
|
|
where `m \in \mathbb Z`.
|
|
|
|
The reconstruction is based on the Euclidean Algorithm. In general,
|
|
`m` is not a prime number, so it is possible that this fails for some
|
|
coefficient. In that case ``None`` is returned.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
hm : PolyElement
|
|
polynomial in `\mathbb Z[t_1, \ldots, t_k][x, z]`
|
|
m : Integer
|
|
modulus, not necessarily prime
|
|
ring : PolyRing
|
|
`\mathbb Q[t_1, \ldots, t_k][x, z]`, `h` will be an element of this
|
|
ring
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
reconstructed polynomial in `\mathbb Q[t_1, \ldots, t_k][x, z]` or
|
|
``None``
|
|
|
|
See also
|
|
========
|
|
|
|
_integer_rational_reconstruction
|
|
|
|
"""
|
|
h = ring.zero
|
|
|
|
if isinstance(ring.domain, PolynomialRing):
|
|
reconstruction = _rational_reconstruction_int_coeffs
|
|
domain = ring.domain.ring
|
|
else:
|
|
reconstruction = _integer_rational_reconstruction
|
|
domain = hm.ring.domain
|
|
|
|
for monom, coeff in hm.iterterms():
|
|
coeffh = reconstruction(coeff, m, domain)
|
|
|
|
if not coeffh:
|
|
return None
|
|
|
|
h[monom] = coeffh
|
|
|
|
return h
|
|
|
|
|
|
def _func_field_modgcd_m(f, g, minpoly):
|
|
r"""
|
|
Compute the GCD of two polynomials in
|
|
`\mathbb Q(t_1, \ldots, t_k)[z]/(m_{\alpha}(z))[x]` using a modular
|
|
algorithm.
|
|
|
|
The algorithm computes the GCD of two polynomials `f` and `g` by
|
|
calculating the GCD in
|
|
`\mathbb Z_p(t_1, \ldots, t_k)[z] / (\check m_{\alpha}(z))[x]` for
|
|
suitable primes `p` and the primitive associate `\check m_{\alpha}(z)`
|
|
of `m_{\alpha}(z)`. Then the coefficients are reconstructed with the
|
|
Chinese Remainder Theorem and Rational Reconstruction. To compute the
|
|
GCD over `\mathbb Z_p(t_1, \ldots, t_k)[z] / (\check m_{\alpha})[x]`,
|
|
the recursive subroutine ``_func_field_modgcd_p`` is used. To verify the
|
|
result in `\mathbb Q(t_1, \ldots, t_k)[z] / (m_{\alpha}(z))[x]`, a
|
|
fraction free trial division is used.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f, g : PolyElement
|
|
polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
|
|
minpoly : PolyElement
|
|
irreducible polynomial in `\mathbb Z[t_1, \ldots, t_k][z]`
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
the primitive associate in `\mathbb Z[t_1, \ldots, t_k][x, z]` of
|
|
the GCD of `f` and `g`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import _func_field_modgcd_m
|
|
>>> from sympy.polys import ring, ZZ
|
|
|
|
>>> R, x, z = ring('x, z', ZZ)
|
|
>>> minpoly = (z**2 - 2).drop(0)
|
|
|
|
>>> f = x**2 + 2*x*z + 2
|
|
>>> g = x + z
|
|
>>> _func_field_modgcd_m(f, g, minpoly)
|
|
x + z
|
|
|
|
>>> D, t = ring('t', ZZ)
|
|
>>> R, x, z = ring('x, z', D)
|
|
>>> minpoly = (z**2-3).drop(0)
|
|
|
|
>>> f = x**2 + (t + 1)*x*z + 3*t
|
|
>>> g = x*z + 3*t
|
|
>>> _func_field_modgcd_m(f, g, minpoly)
|
|
x + t*z
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Hoeij04]_
|
|
|
|
See also
|
|
========
|
|
|
|
_func_field_modgcd_p
|
|
|
|
"""
|
|
ring = f.ring
|
|
domain = ring.domain
|
|
|
|
if isinstance(domain, PolynomialRing):
|
|
k = domain.ngens
|
|
QQdomain = domain.ring.clone(domain=domain.domain.get_field())
|
|
QQring = ring.clone(domain=QQdomain)
|
|
else:
|
|
k = 0
|
|
QQring = ring.clone(domain=ring.domain.get_field())
|
|
|
|
cf, f = f.primitive()
|
|
cg, g = g.primitive()
|
|
|
|
# polynomial in Z[t_1, ..., t_k][z]
|
|
gamma = ring.dmp_LC(f) * ring.dmp_LC(g)
|
|
# polynomial in Z[t_1, ..., t_k]
|
|
delta = minpoly.LC
|
|
|
|
p = 1
|
|
primes = []
|
|
hplist = []
|
|
LMlist = []
|
|
|
|
while True:
|
|
p = nextprime(p)
|
|
|
|
if gamma.trunc_ground(p) == 0:
|
|
continue
|
|
|
|
if k == 0:
|
|
test = (delta % p == 0)
|
|
else:
|
|
test = (delta.trunc_ground(p) == 0)
|
|
|
|
if test:
|
|
continue
|
|
|
|
fp = f.trunc_ground(p)
|
|
gp = g.trunc_ground(p)
|
|
minpolyp = minpoly.trunc_ground(p)
|
|
|
|
hp = _func_field_modgcd_p(fp, gp, minpolyp, p)
|
|
|
|
if hp is None:
|
|
continue
|
|
|
|
if hp == 1:
|
|
return ring.one
|
|
|
|
LM = [hp.degree()] + [0]*k
|
|
if k > 0:
|
|
for monom, coeff in hp.iterterms():
|
|
if monom[0] == LM[0] and coeff.LM > tuple(LM[1:]):
|
|
LM[1:] = coeff.LM
|
|
|
|
hm = hp
|
|
m = p
|
|
|
|
for q, hq, LMhq in zip(primes, hplist, LMlist):
|
|
if LMhq == LM:
|
|
hm = _chinese_remainder_reconstruction_multivariate(hq, hm, q, m)
|
|
m *= q
|
|
|
|
primes.append(p)
|
|
hplist.append(hp)
|
|
LMlist.append(LM)
|
|
|
|
hm = _rational_reconstruction_int_coeffs(hm, m, QQring)
|
|
|
|
if hm is None:
|
|
continue
|
|
|
|
if k == 0:
|
|
h = hm.clear_denoms()[1]
|
|
else:
|
|
den = domain.domain.one
|
|
for coeff in hm.itercoeffs():
|
|
den = domain.domain.lcm(den, coeff.clear_denoms()[0])
|
|
h = hm.mul_ground(den)
|
|
|
|
# convert back to Z[t_1, ..., t_k][x, z] from Q[t_1, ..., t_k][x, z]
|
|
h = h.set_ring(ring)
|
|
h = h.primitive()[1]
|
|
|
|
if not (_trial_division(f.mul_ground(cf), h, minpoly) or
|
|
_trial_division(g.mul_ground(cg), h, minpoly)):
|
|
return h
|
|
|
|
|
|
def _to_ZZ_poly(f, ring):
|
|
r"""
|
|
Compute an associate of a polynomial
|
|
`f \in \mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]` in
|
|
`\mathbb Z[x_1, \ldots, x_{n-1}][z] / (\check m_{\alpha}(z))[x_0]`,
|
|
where `\check m_{\alpha}(z) \in \mathbb Z[z]` is the primitive associate
|
|
of the minimal polynomial `m_{\alpha}(z)` of `\alpha` over
|
|
`\mathbb Q`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
|
|
ring : PolyRing
|
|
`\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
|
|
|
|
Returns
|
|
=======
|
|
|
|
f_ : PolyElement
|
|
associate of `f` in
|
|
`\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
|
|
|
|
"""
|
|
f_ = ring.zero
|
|
|
|
if isinstance(ring.domain, PolynomialRing):
|
|
domain = ring.domain.domain
|
|
else:
|
|
domain = ring.domain
|
|
|
|
den = domain.one
|
|
|
|
for coeff in f.itercoeffs():
|
|
for c in coeff.rep:
|
|
if c:
|
|
den = domain.lcm(den, c.denominator)
|
|
|
|
for monom, coeff in f.iterterms():
|
|
coeff = coeff.rep
|
|
m = ring.domain.one
|
|
if isinstance(ring.domain, PolynomialRing):
|
|
m = m.mul_monom(monom[1:])
|
|
n = len(coeff)
|
|
|
|
for i in range(n):
|
|
if coeff[i]:
|
|
c = domain(coeff[i] * den) * m
|
|
|
|
if (monom[0], n-i-1) not in f_:
|
|
f_[(monom[0], n-i-1)] = c
|
|
else:
|
|
f_[(monom[0], n-i-1)] += c
|
|
|
|
return f_
|
|
|
|
|
|
def _to_ANP_poly(f, ring):
|
|
r"""
|
|
Convert a polynomial
|
|
`f \in \mathbb Z[x_1, \ldots, x_{n-1}][z]/(\check m_{\alpha}(z))[x_0]`
|
|
to a polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`,
|
|
where `\check m_{\alpha}(z) \in \mathbb Z[z]` is the primitive associate
|
|
of the minimal polynomial `m_{\alpha}(z)` of `\alpha` over
|
|
`\mathbb Q`.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f : PolyElement
|
|
polynomial in `\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
|
|
ring : PolyRing
|
|
`\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
|
|
|
|
Returns
|
|
=======
|
|
|
|
f_ : PolyElement
|
|
polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
|
|
|
|
"""
|
|
domain = ring.domain
|
|
f_ = ring.zero
|
|
|
|
if isinstance(f.ring.domain, PolynomialRing):
|
|
for monom, coeff in f.iterterms():
|
|
for mon, coef in coeff.iterterms():
|
|
m = (monom[0],) + mon
|
|
c = domain([domain.domain(coef)] + [0]*monom[1])
|
|
|
|
if m not in f_:
|
|
f_[m] = c
|
|
else:
|
|
f_[m] += c
|
|
|
|
else:
|
|
for monom, coeff in f.iterterms():
|
|
m = (monom[0],)
|
|
c = domain([domain.domain(coeff)] + [0]*monom[1])
|
|
|
|
if m not in f_:
|
|
f_[m] = c
|
|
else:
|
|
f_[m] += c
|
|
|
|
return f_
|
|
|
|
|
|
def _minpoly_from_dense(minpoly, ring):
|
|
r"""
|
|
Change representation of the minimal polynomial from ``DMP`` to
|
|
``PolyElement`` for a given ring.
|
|
"""
|
|
minpoly_ = ring.zero
|
|
|
|
for monom, coeff in minpoly.terms():
|
|
minpoly_[monom] = ring.domain(coeff)
|
|
|
|
return minpoly_
|
|
|
|
|
|
def _primitive_in_x0(f):
|
|
r"""
|
|
Compute the content in `x_0` and the primitive part of a polynomial `f`
|
|
in
|
|
`\mathbb Q(\alpha)[x_0, x_1, \ldots, x_{n-1}] \cong \mathbb Q(\alpha)[x_1, \ldots, x_{n-1}][x_0]`.
|
|
"""
|
|
fring = f.ring
|
|
ring = fring.drop_to_ground(*range(1, fring.ngens))
|
|
dom = ring.domain.ring
|
|
f_ = ring(f.as_expr())
|
|
cont = dom.zero
|
|
|
|
for coeff in f_.itercoeffs():
|
|
cont = func_field_modgcd(cont, coeff)[0]
|
|
if cont == dom.one:
|
|
return cont, f
|
|
|
|
return cont, f.quo(cont.set_ring(fring))
|
|
|
|
|
|
# TODO: add support for algebraic function fields
|
|
def func_field_modgcd(f, g):
|
|
r"""
|
|
Compute the GCD of two polynomials `f` and `g` in
|
|
`\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]` using a modular algorithm.
|
|
|
|
The algorithm first computes the primitive associate
|
|
`\check m_{\alpha}(z)` of the minimal polynomial `m_{\alpha}` in
|
|
`\mathbb{Z}[z]` and the primitive associates of `f` and `g` in
|
|
`\mathbb{Z}[x_1, \ldots, x_{n-1}][z]/(\check m_{\alpha})[x_0]`. Then it
|
|
computes the GCD in
|
|
`\mathbb Q(x_1, \ldots, x_{n-1})[z]/(m_{\alpha}(z))[x_0]`.
|
|
This is done by calculating the GCD in
|
|
`\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]` for
|
|
suitable primes `p` and then reconstructing the coefficients with the
|
|
Chinese Remainder Theorem and Rational Reconstuction. The GCD over
|
|
`\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]` is
|
|
computed with a recursive subroutine, which evaluates the polynomials at
|
|
`x_{n-1} = a` for suitable evaluation points `a \in \mathbb Z_p` and
|
|
then calls itself recursively until the ground domain does no longer
|
|
contain any parameters. For
|
|
`\mathbb{Z}_p[z]/(\check m_{\alpha}(z))[x_0]` the Euclidean Algorithm is
|
|
used. The results of those recursive calls are then interpolated and
|
|
Rational Function Reconstruction is used to obtain the correct
|
|
coefficients. The results, both in
|
|
`\mathbb Q(x_1, \ldots, x_{n-1})[z]/(m_{\alpha}(z))[x_0]` and
|
|
`\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]`, are
|
|
verified by a fraction free trial division.
|
|
|
|
Apart from the above GCD computation some GCDs in
|
|
`\mathbb Q(\alpha)[x_1, \ldots, x_{n-1}]` have to be calculated,
|
|
because treating the polynomials as univariate ones can result in
|
|
a spurious content of the GCD. For this ``func_field_modgcd`` is
|
|
called recursively.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
f, g : PolyElement
|
|
polynomials in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
|
|
|
|
Returns
|
|
=======
|
|
|
|
h : PolyElement
|
|
monic GCD of the polynomials `f` and `g`
|
|
cff : PolyElement
|
|
cofactor of `f`, i.e. `\frac f h`
|
|
cfg : PolyElement
|
|
cofactor of `g`, i.e. `\frac g h`
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.polys.modulargcd import func_field_modgcd
|
|
>>> from sympy.polys import AlgebraicField, QQ, ring
|
|
>>> from sympy import sqrt
|
|
|
|
>>> A = AlgebraicField(QQ, sqrt(2))
|
|
>>> R, x = ring('x', A)
|
|
|
|
>>> f = x**2 - 2
|
|
>>> g = x + sqrt(2)
|
|
|
|
>>> h, cff, cfg = func_field_modgcd(f, g)
|
|
|
|
>>> h == x + sqrt(2)
|
|
True
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
>>> R, x, y = ring('x, y', A)
|
|
|
|
>>> f = x**2 + 2*sqrt(2)*x*y + 2*y**2
|
|
>>> g = x + sqrt(2)*y
|
|
|
|
>>> h, cff, cfg = func_field_modgcd(f, g)
|
|
|
|
>>> h == x + sqrt(2)*y
|
|
True
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
>>> f = x + sqrt(2)*y
|
|
>>> g = x + y
|
|
|
|
>>> h, cff, cfg = func_field_modgcd(f, g)
|
|
|
|
>>> h == R.one
|
|
True
|
|
>>> cff * h == f
|
|
True
|
|
>>> cfg * h == g
|
|
True
|
|
|
|
References
|
|
==========
|
|
|
|
1. [Hoeij04]_
|
|
|
|
"""
|
|
ring = f.ring
|
|
domain = ring.domain
|
|
n = ring.ngens
|
|
|
|
assert ring == g.ring and domain.is_Algebraic
|
|
|
|
result = _trivial_gcd(f, g)
|
|
if result is not None:
|
|
return result
|
|
|
|
z = Dummy('z')
|
|
|
|
ZZring = ring.clone(symbols=ring.symbols + (z,), domain=domain.domain.get_ring())
|
|
|
|
if n == 1:
|
|
f_ = _to_ZZ_poly(f, ZZring)
|
|
g_ = _to_ZZ_poly(g, ZZring)
|
|
minpoly = ZZring.drop(0).from_dense(domain.mod.rep)
|
|
|
|
h = _func_field_modgcd_m(f_, g_, minpoly)
|
|
h = _to_ANP_poly(h, ring)
|
|
|
|
else:
|
|
# contx0f in Q(a)[x_1, ..., x_{n-1}], f in Q(a)[x_0, ..., x_{n-1}]
|
|
contx0f, f = _primitive_in_x0(f)
|
|
contx0g, g = _primitive_in_x0(g)
|
|
contx0h = func_field_modgcd(contx0f, contx0g)[0]
|
|
|
|
ZZring_ = ZZring.drop_to_ground(*range(1, n))
|
|
|
|
f_ = _to_ZZ_poly(f, ZZring_)
|
|
g_ = _to_ZZ_poly(g, ZZring_)
|
|
minpoly = _minpoly_from_dense(domain.mod, ZZring_.drop(0))
|
|
|
|
h = _func_field_modgcd_m(f_, g_, minpoly)
|
|
h = _to_ANP_poly(h, ring)
|
|
|
|
contx0h_, h = _primitive_in_x0(h)
|
|
h *= contx0h.set_ring(ring)
|
|
f *= contx0f.set_ring(ring)
|
|
g *= contx0g.set_ring(ring)
|
|
|
|
h = h.quo_ground(h.LC)
|
|
|
|
return h, f.quo(h), g.quo(h)
|