Spaces:
Sleeping
Sleeping
"""Minimal polynomials for algebraic numbers.""" | |
from functools import reduce | |
from sympy.core.add import Add | |
from sympy.core.exprtools import Factors | |
from sympy.core.function import expand_mul, expand_multinomial, _mexpand | |
from sympy.core.mul import Mul | |
from sympy.core.numbers import (I, Rational, pi, _illegal) | |
from sympy.core.singleton import S | |
from sympy.core.symbol import Dummy | |
from sympy.core.sympify import sympify | |
from sympy.core.traversal import preorder_traversal | |
from sympy.functions.elementary.exponential import exp | |
from sympy.functions.elementary.miscellaneous import sqrt, cbrt | |
from sympy.functions.elementary.trigonometric import cos, sin, tan | |
from sympy.ntheory.factor_ import divisors | |
from sympy.utilities.iterables import subsets | |
from sympy.polys.domains import ZZ, QQ, FractionField | |
from sympy.polys.orthopolys import dup_chebyshevt | |
from sympy.polys.polyerrors import ( | |
NotAlgebraic, | |
GeneratorsError, | |
) | |
from sympy.polys.polytools import ( | |
Poly, PurePoly, invert, factor_list, groebner, resultant, | |
degree, poly_from_expr, parallel_poly_from_expr, lcm | |
) | |
from sympy.polys.polyutils import dict_from_expr, expr_from_dict | |
from sympy.polys.ring_series import rs_compose_add | |
from sympy.polys.rings import ring | |
from sympy.polys.rootoftools import CRootOf | |
from sympy.polys.specialpolys import cyclotomic_poly | |
from sympy.utilities import ( | |
numbered_symbols, public, sift | |
) | |
def _choose_factor(factors, x, v, dom=QQ, prec=200, bound=5): | |
""" | |
Return a factor having root ``v`` | |
It is assumed that one of the factors has root ``v``. | |
""" | |
if isinstance(factors[0], tuple): | |
factors = [f[0] for f in factors] | |
if len(factors) == 1: | |
return factors[0] | |
prec1 = 10 | |
points = {} | |
symbols = dom.symbols if hasattr(dom, 'symbols') else [] | |
while prec1 <= prec: | |
# when dealing with non-Rational numbers we usually evaluate | |
# with `subs` argument but we only need a ballpark evaluation | |
fe = [f.as_expr().xreplace({x:v}) for f in factors] | |
if v.is_number: | |
fe = [f.n(prec) for f in fe] | |
# assign integers [0, n) to symbols (if any) | |
for n in subsets(range(bound), k=len(symbols), repetition=True): | |
for s, i in zip(symbols, n): | |
points[s] = i | |
# evaluate the expression at these points | |
candidates = [(abs(f.subs(points).n(prec1)), i) | |
for i,f in enumerate(fe)] | |
# if we get invalid numbers (e.g. from division by zero) | |
# we try again | |
if any(i in _illegal for i, _ in candidates): | |
continue | |
# find the smallest two -- if they differ significantly | |
# then we assume we have found the factor that becomes | |
# 0 when v is substituted into it | |
can = sorted(candidates) | |
(a, ix), (b, _) = can[:2] | |
if b > a * 10**6: # XXX what to use? | |
return factors[ix] | |
prec1 *= 2 | |
raise NotImplementedError("multiple candidates for the minimal polynomial of %s" % v) | |
def _is_sum_surds(p): | |
args = p.args if p.is_Add else [p] | |
for y in args: | |
if not ((y**2).is_Rational and y.is_extended_real): | |
return False | |
return True | |
def _separate_sq(p): | |
""" | |
helper function for ``_minimal_polynomial_sq`` | |
It selects a rational ``g`` such that the polynomial ``p`` | |
consists of a sum of terms whose surds squared have gcd equal to ``g`` | |
and a sum of terms with surds squared prime with ``g``; | |
then it takes the field norm to eliminate ``sqrt(g)`` | |
See simplify.simplify.split_surds and polytools.sqf_norm. | |
Examples | |
======== | |
>>> from sympy import sqrt | |
>>> from sympy.abc import x | |
>>> from sympy.polys.numberfields.minpoly import _separate_sq | |
>>> p= -x + sqrt(2) + sqrt(3) + sqrt(7) | |
>>> p = _separate_sq(p); p | |
-x**2 + 2*sqrt(3)*x + 2*sqrt(7)*x - 2*sqrt(21) - 8 | |
>>> p = _separate_sq(p); p | |
-x**4 + 4*sqrt(7)*x**3 - 32*x**2 + 8*sqrt(7)*x + 20 | |
>>> p = _separate_sq(p); p | |
-x**8 + 48*x**6 - 536*x**4 + 1728*x**2 - 400 | |
""" | |
def is_sqrt(expr): | |
return expr.is_Pow and expr.exp is S.Half | |
# p = c1*sqrt(q1) + ... + cn*sqrt(qn) -> a = [(c1, q1), .., (cn, qn)] | |
a = [] | |
for y in p.args: | |
if not y.is_Mul: | |
if is_sqrt(y): | |
a.append((S.One, y**2)) | |
elif y.is_Atom: | |
a.append((y, S.One)) | |
elif y.is_Pow and y.exp.is_integer: | |
a.append((y, S.One)) | |
else: | |
raise NotImplementedError | |
else: | |
T, F = sift(y.args, is_sqrt, binary=True) | |
a.append((Mul(*F), Mul(*T)**2)) | |
a.sort(key=lambda z: z[1]) | |
if a[-1][1] is S.One: | |
# there are no surds | |
return p | |
surds = [z for y, z in a] | |
for i in range(len(surds)): | |
if surds[i] != 1: | |
break | |
from sympy.simplify.radsimp import _split_gcd | |
g, b1, b2 = _split_gcd(*surds[i:]) | |
a1 = [] | |
a2 = [] | |
for y, z in a: | |
if z in b1: | |
a1.append(y*z**S.Half) | |
else: | |
a2.append(y*z**S.Half) | |
p1 = Add(*a1) | |
p2 = Add(*a2) | |
p = _mexpand(p1**2) - _mexpand(p2**2) | |
return p | |
def _minimal_polynomial_sq(p, n, x): | |
""" | |
Returns the minimal polynomial for the ``nth-root`` of a sum of surds | |
or ``None`` if it fails. | |
Parameters | |
========== | |
p : sum of surds | |
n : positive integer | |
x : variable of the returned polynomial | |
Examples | |
======== | |
>>> from sympy.polys.numberfields.minpoly import _minimal_polynomial_sq | |
>>> from sympy import sqrt | |
>>> from sympy.abc import x | |
>>> q = 1 + sqrt(2) + sqrt(3) | |
>>> _minimal_polynomial_sq(q, 3, x) | |
x**12 - 4*x**9 - 4*x**6 + 16*x**3 - 8 | |
""" | |
p = sympify(p) | |
n = sympify(n) | |
if not n.is_Integer or not n > 0 or not _is_sum_surds(p): | |
return None | |
pn = p**Rational(1, n) | |
# eliminate the square roots | |
p -= x | |
while 1: | |
p1 = _separate_sq(p) | |
if p1 is p: | |
p = p1.subs({x:x**n}) | |
break | |
else: | |
p = p1 | |
# _separate_sq eliminates field extensions in a minimal way, so that | |
# if n = 1 then `p = constant*(minimal_polynomial(p))` | |
# if n > 1 it contains the minimal polynomial as a factor. | |
if n == 1: | |
p1 = Poly(p) | |
if p.coeff(x**p1.degree(x)) < 0: | |
p = -p | |
p = p.primitive()[1] | |
return p | |
# by construction `p` has root `pn` | |
# the minimal polynomial is the factor vanishing in x = pn | |
factors = factor_list(p)[1] | |
result = _choose_factor(factors, x, pn) | |
return result | |
def _minpoly_op_algebraic_element(op, ex1, ex2, x, dom, mp1=None, mp2=None): | |
""" | |
return the minimal polynomial for ``op(ex1, ex2)`` | |
Parameters | |
========== | |
op : operation ``Add`` or ``Mul`` | |
ex1, ex2 : expressions for the algebraic elements | |
x : indeterminate of the polynomials | |
dom: ground domain | |
mp1, mp2 : minimal polynomials for ``ex1`` and ``ex2`` or None | |
Examples | |
======== | |
>>> from sympy import sqrt, Add, Mul, QQ | |
>>> from sympy.polys.numberfields.minpoly import _minpoly_op_algebraic_element | |
>>> from sympy.abc import x, y | |
>>> p1 = sqrt(sqrt(2) + 1) | |
>>> p2 = sqrt(sqrt(2) - 1) | |
>>> _minpoly_op_algebraic_element(Mul, p1, p2, x, QQ) | |
x - 1 | |
>>> q1 = sqrt(y) | |
>>> q2 = 1 / y | |
>>> _minpoly_op_algebraic_element(Add, q1, q2, x, QQ.frac_field(y)) | |
x**2*y**2 - 2*x*y - y**3 + 1 | |
References | |
========== | |
.. [1] https://en.wikipedia.org/wiki/Resultant | |
.. [2] I.M. Isaacs, Proc. Amer. Math. Soc. 25 (1970), 638 | |
"Degrees of sums in a separable field extension". | |
""" | |
y = Dummy(str(x)) | |
if mp1 is None: | |
mp1 = _minpoly_compose(ex1, x, dom) | |
if mp2 is None: | |
mp2 = _minpoly_compose(ex2, y, dom) | |
else: | |
mp2 = mp2.subs({x: y}) | |
if op is Add: | |
# mp1a = mp1.subs({x: x - y}) | |
if dom == QQ: | |
R, X = ring('X', QQ) | |
p1 = R(dict_from_expr(mp1)[0]) | |
p2 = R(dict_from_expr(mp2)[0]) | |
else: | |
(p1, p2), _ = parallel_poly_from_expr((mp1, x - y), x, y) | |
r = p1.compose(p2) | |
mp1a = r.as_expr() | |
elif op is Mul: | |
mp1a = _muly(mp1, x, y) | |
else: | |
raise NotImplementedError('option not available') | |
if op is Mul or dom != QQ: | |
r = resultant(mp1a, mp2, gens=[y, x]) | |
else: | |
r = rs_compose_add(p1, p2) | |
r = expr_from_dict(r.as_expr_dict(), x) | |
deg1 = degree(mp1, x) | |
deg2 = degree(mp2, y) | |
if op is Mul and deg1 == 1 or deg2 == 1: | |
# if deg1 = 1, then mp1 = x - a; mp1a = x - y - a; | |
# r = mp2(x - a), so that `r` is irreducible | |
return r | |
r = Poly(r, x, domain=dom) | |
_, factors = r.factor_list() | |
res = _choose_factor(factors, x, op(ex1, ex2), dom) | |
return res.as_expr() | |
def _invertx(p, x): | |
""" | |
Returns ``expand_mul(x**degree(p, x)*p.subs(x, 1/x))`` | |
""" | |
p1 = poly_from_expr(p, x)[0] | |
n = degree(p1) | |
a = [c * x**(n - i) for (i,), c in p1.terms()] | |
return Add(*a) | |
def _muly(p, x, y): | |
""" | |
Returns ``_mexpand(y**deg*p.subs({x:x / y}))`` | |
""" | |
p1 = poly_from_expr(p, x)[0] | |
n = degree(p1) | |
a = [c * x**i * y**(n - i) for (i,), c in p1.terms()] | |
return Add(*a) | |
def _minpoly_pow(ex, pw, x, dom, mp=None): | |
""" | |
Returns ``minpoly(ex**pw, x)`` | |
Parameters | |
========== | |
ex : algebraic element | |
pw : rational number | |
x : indeterminate of the polynomial | |
dom: ground domain | |
mp : minimal polynomial of ``p`` | |
Examples | |
======== | |
>>> from sympy import sqrt, QQ, Rational | |
>>> from sympy.polys.numberfields.minpoly import _minpoly_pow, minpoly | |
>>> from sympy.abc import x, y | |
>>> p = sqrt(1 + sqrt(2)) | |
>>> _minpoly_pow(p, 2, x, QQ) | |
x**2 - 2*x - 1 | |
>>> minpoly(p**2, x) | |
x**2 - 2*x - 1 | |
>>> _minpoly_pow(y, Rational(1, 3), x, QQ.frac_field(y)) | |
x**3 - y | |
>>> minpoly(y**Rational(1, 3), x) | |
x**3 - y | |
""" | |
pw = sympify(pw) | |
if not mp: | |
mp = _minpoly_compose(ex, x, dom) | |
if not pw.is_rational: | |
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) | |
if pw < 0: | |
if mp == x: | |
raise ZeroDivisionError('%s is zero' % ex) | |
mp = _invertx(mp, x) | |
if pw == -1: | |
return mp | |
pw = -pw | |
ex = 1/ex | |
y = Dummy(str(x)) | |
mp = mp.subs({x: y}) | |
n, d = pw.as_numer_denom() | |
res = Poly(resultant(mp, x**d - y**n, gens=[y]), x, domain=dom) | |
_, factors = res.factor_list() | |
res = _choose_factor(factors, x, ex**pw, dom) | |
return res.as_expr() | |
def _minpoly_add(x, dom, *a): | |
""" | |
returns ``minpoly(Add(*a), dom, x)`` | |
""" | |
mp = _minpoly_op_algebraic_element(Add, a[0], a[1], x, dom) | |
p = a[0] + a[1] | |
for px in a[2:]: | |
mp = _minpoly_op_algebraic_element(Add, p, px, x, dom, mp1=mp) | |
p = p + px | |
return mp | |
def _minpoly_mul(x, dom, *a): | |
""" | |
returns ``minpoly(Mul(*a), dom, x)`` | |
""" | |
mp = _minpoly_op_algebraic_element(Mul, a[0], a[1], x, dom) | |
p = a[0] * a[1] | |
for px in a[2:]: | |
mp = _minpoly_op_algebraic_element(Mul, p, px, x, dom, mp1=mp) | |
p = p * px | |
return mp | |
def _minpoly_sin(ex, x): | |
""" | |
Returns the minimal polynomial of ``sin(ex)`` | |
see https://mathworld.wolfram.com/TrigonometryAngles.html | |
""" | |
c, a = ex.args[0].as_coeff_Mul() | |
if a is pi: | |
if c.is_rational: | |
n = c.q | |
q = sympify(n) | |
if q.is_prime: | |
# for a = pi*p/q with q odd prime, using chebyshevt | |
# write sin(q*a) = mp(sin(a))*sin(a); | |
# the roots of mp(x) are sin(pi*p/q) for p = 1,..., q - 1 | |
a = dup_chebyshevt(n, ZZ) | |
return Add(*[x**(n - i - 1)*a[i] for i in range(n)]) | |
if c.p == 1: | |
if q == 9: | |
return 64*x**6 - 96*x**4 + 36*x**2 - 3 | |
if n % 2 == 1: | |
# for a = pi*p/q with q odd, use | |
# sin(q*a) = 0 to see that the minimal polynomial must be | |
# a factor of dup_chebyshevt(n, ZZ) | |
a = dup_chebyshevt(n, ZZ) | |
a = [x**(n - i)*a[i] for i in range(n + 1)] | |
r = Add(*a) | |
_, factors = factor_list(r) | |
res = _choose_factor(factors, x, ex) | |
return res | |
expr = ((1 - cos(2*c*pi))/2)**S.Half | |
res = _minpoly_compose(expr, x, QQ) | |
return res | |
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) | |
def _minpoly_cos(ex, x): | |
""" | |
Returns the minimal polynomial of ``cos(ex)`` | |
see https://mathworld.wolfram.com/TrigonometryAngles.html | |
""" | |
c, a = ex.args[0].as_coeff_Mul() | |
if a is pi: | |
if c.is_rational: | |
if c.p == 1: | |
if c.q == 7: | |
return 8*x**3 - 4*x**2 - 4*x + 1 | |
if c.q == 9: | |
return 8*x**3 - 6*x - 1 | |
elif c.p == 2: | |
q = sympify(c.q) | |
if q.is_prime: | |
s = _minpoly_sin(ex, x) | |
return _mexpand(s.subs({x:sqrt((1 - x)/2)})) | |
# for a = pi*p/q, cos(q*a) =T_q(cos(a)) = (-1)**p | |
n = int(c.q) | |
a = dup_chebyshevt(n, ZZ) | |
a = [x**(n - i)*a[i] for i in range(n + 1)] | |
r = Add(*a) - (-1)**c.p | |
_, factors = factor_list(r) | |
res = _choose_factor(factors, x, ex) | |
return res | |
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) | |
def _minpoly_tan(ex, x): | |
""" | |
Returns the minimal polynomial of ``tan(ex)`` | |
see https://github.com/sympy/sympy/issues/21430 | |
""" | |
c, a = ex.args[0].as_coeff_Mul() | |
if a is pi: | |
if c.is_rational: | |
c = c * 2 | |
n = int(c.q) | |
a = n if c.p % 2 == 0 else 1 | |
terms = [] | |
for k in range((c.p+1)%2, n+1, 2): | |
terms.append(a*x**k) | |
a = -(a*(n-k-1)*(n-k)) // ((k+1)*(k+2)) | |
r = Add(*terms) | |
_, factors = factor_list(r) | |
res = _choose_factor(factors, x, ex) | |
return res | |
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) | |
def _minpoly_exp(ex, x): | |
""" | |
Returns the minimal polynomial of ``exp(ex)`` | |
""" | |
c, a = ex.args[0].as_coeff_Mul() | |
if a == I*pi: | |
if c.is_rational: | |
q = sympify(c.q) | |
if c.p == 1 or c.p == -1: | |
if q == 3: | |
return x**2 - x + 1 | |
if q == 4: | |
return x**4 + 1 | |
if q == 6: | |
return x**4 - x**2 + 1 | |
if q == 8: | |
return x**8 + 1 | |
if q == 9: | |
return x**6 - x**3 + 1 | |
if q == 10: | |
return x**8 - x**6 + x**4 - x**2 + 1 | |
if q.is_prime: | |
s = 0 | |
for i in range(q): | |
s += (-x)**i | |
return s | |
# x**(2*q) = product(factors) | |
factors = [cyclotomic_poly(i, x) for i in divisors(2*q)] | |
mp = _choose_factor(factors, x, ex) | |
return mp | |
else: | |
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) | |
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) | |
def _minpoly_rootof(ex, x): | |
""" | |
Returns the minimal polynomial of a ``CRootOf`` object. | |
""" | |
p = ex.expr | |
p = p.subs({ex.poly.gens[0]:x}) | |
_, factors = factor_list(p, x) | |
result = _choose_factor(factors, x, ex) | |
return result | |
def _minpoly_compose(ex, x, dom): | |
""" | |
Computes the minimal polynomial of an algebraic element | |
using operations on minimal polynomials | |
Examples | |
======== | |
>>> from sympy import minimal_polynomial, sqrt, Rational | |
>>> from sympy.abc import x, y | |
>>> minimal_polynomial(sqrt(2) + 3*Rational(1, 3), x, compose=True) | |
x**2 - 2*x - 1 | |
>>> minimal_polynomial(sqrt(y) + 1/y, x, compose=True) | |
x**2*y**2 - 2*x*y - y**3 + 1 | |
""" | |
if ex.is_Rational: | |
return ex.q*x - ex.p | |
if ex is I: | |
_, factors = factor_list(x**2 + 1, x, domain=dom) | |
return x**2 + 1 if len(factors) == 1 else x - I | |
if ex is S.GoldenRatio: | |
_, factors = factor_list(x**2 - x - 1, x, domain=dom) | |
if len(factors) == 1: | |
return x**2 - x - 1 | |
else: | |
return _choose_factor(factors, x, (1 + sqrt(5))/2, dom=dom) | |
if ex is S.TribonacciConstant: | |
_, factors = factor_list(x**3 - x**2 - x - 1, x, domain=dom) | |
if len(factors) == 1: | |
return x**3 - x**2 - x - 1 | |
else: | |
fac = (1 + cbrt(19 - 3*sqrt(33)) + cbrt(19 + 3*sqrt(33))) / 3 | |
return _choose_factor(factors, x, fac, dom=dom) | |
if hasattr(dom, 'symbols') and ex in dom.symbols: | |
return x - ex | |
if dom.is_QQ and _is_sum_surds(ex): | |
# eliminate the square roots | |
ex -= x | |
while 1: | |
ex1 = _separate_sq(ex) | |
if ex1 is ex: | |
return ex | |
else: | |
ex = ex1 | |
if ex.is_Add: | |
res = _minpoly_add(x, dom, *ex.args) | |
elif ex.is_Mul: | |
f = Factors(ex).factors | |
r = sift(f.items(), lambda itx: itx[0].is_Rational and itx[1].is_Rational) | |
if r[True] and dom == QQ: | |
ex1 = Mul(*[bx**ex for bx, ex in r[False] + r[None]]) | |
r1 = dict(r[True]) | |
dens = [y.q for y in r1.values()] | |
lcmdens = reduce(lcm, dens, 1) | |
neg1 = S.NegativeOne | |
expn1 = r1.pop(neg1, S.Zero) | |
nums = [base**(y.p*lcmdens // y.q) for base, y in r1.items()] | |
ex2 = Mul(*nums) | |
mp1 = minimal_polynomial(ex1, x) | |
# use the fact that in SymPy canonicalization products of integers | |
# raised to rational powers are organized in relatively prime | |
# bases, and that in ``base**(n/d)`` a perfect power is | |
# simplified with the root | |
# Powers of -1 have to be treated separately to preserve sign. | |
mp2 = ex2.q*x**lcmdens - ex2.p*neg1**(expn1*lcmdens) | |
ex2 = neg1**expn1 * ex2**Rational(1, lcmdens) | |
res = _minpoly_op_algebraic_element(Mul, ex1, ex2, x, dom, mp1=mp1, mp2=mp2) | |
else: | |
res = _minpoly_mul(x, dom, *ex.args) | |
elif ex.is_Pow: | |
res = _minpoly_pow(ex.base, ex.exp, x, dom) | |
elif ex.__class__ is sin: | |
res = _minpoly_sin(ex, x) | |
elif ex.__class__ is cos: | |
res = _minpoly_cos(ex, x) | |
elif ex.__class__ is tan: | |
res = _minpoly_tan(ex, x) | |
elif ex.__class__ is exp: | |
res = _minpoly_exp(ex, x) | |
elif ex.__class__ is CRootOf: | |
res = _minpoly_rootof(ex, x) | |
else: | |
raise NotAlgebraic("%s does not seem to be an algebraic element" % ex) | |
return res | |
def minimal_polynomial(ex, x=None, compose=True, polys=False, domain=None): | |
""" | |
Computes the minimal polynomial of an algebraic element. | |
Parameters | |
========== | |
ex : Expr | |
Element or expression whose minimal polynomial is to be calculated. | |
x : Symbol, optional | |
Independent variable of the minimal polynomial | |
compose : boolean, optional (default=True) | |
Method to use for computing minimal polynomial. If ``compose=True`` | |
(default) then ``_minpoly_compose`` is used, if ``compose=False`` then | |
groebner bases are used. | |
polys : boolean, optional (default=False) | |
If ``True`` returns a ``Poly`` object else an ``Expr`` object. | |
domain : Domain, optional | |
Ground domain | |
Notes | |
===== | |
By default ``compose=True``, the minimal polynomial of the subexpressions of ``ex`` | |
are computed, then the arithmetic operations on them are performed using the resultant | |
and factorization. | |
If ``compose=False``, a bottom-up algorithm is used with ``groebner``. | |
The default algorithm stalls less frequently. | |
If no ground domain is given, it will be generated automatically from the expression. | |
Examples | |
======== | |
>>> from sympy import minimal_polynomial, sqrt, solve, QQ | |
>>> from sympy.abc import x, y | |
>>> minimal_polynomial(sqrt(2), x) | |
x**2 - 2 | |
>>> minimal_polynomial(sqrt(2), x, domain=QQ.algebraic_field(sqrt(2))) | |
x - sqrt(2) | |
>>> minimal_polynomial(sqrt(2) + sqrt(3), x) | |
x**4 - 10*x**2 + 1 | |
>>> minimal_polynomial(solve(x**3 + x + 3)[0], x) | |
x**3 + x + 3 | |
>>> minimal_polynomial(sqrt(y), x) | |
x**2 - y | |
""" | |
ex = sympify(ex) | |
if ex.is_number: | |
# not sure if it's always needed but try it for numbers (issue 8354) | |
ex = _mexpand(ex, recursive=True) | |
for expr in preorder_traversal(ex): | |
if expr.is_AlgebraicNumber: | |
compose = False | |
break | |
if x is not None: | |
x, cls = sympify(x), Poly | |
else: | |
x, cls = Dummy('x'), PurePoly | |
if not domain: | |
if ex.free_symbols: | |
domain = FractionField(QQ, list(ex.free_symbols)) | |
else: | |
domain = QQ | |
if hasattr(domain, 'symbols') and x in domain.symbols: | |
raise GeneratorsError("the variable %s is an element of the ground " | |
"domain %s" % (x, domain)) | |
if compose: | |
result = _minpoly_compose(ex, x, domain) | |
result = result.primitive()[1] | |
c = result.coeff(x**degree(result, x)) | |
if c.is_negative: | |
result = expand_mul(-result) | |
return cls(result, x, field=True) if polys else result.collect(x) | |
if not domain.is_QQ: | |
raise NotImplementedError("groebner method only works for QQ") | |
result = _minpoly_groebner(ex, x, cls) | |
return cls(result, x, field=True) if polys else result.collect(x) | |
def _minpoly_groebner(ex, x, cls): | |
""" | |
Computes the minimal polynomial of an algebraic number | |
using Groebner bases | |
Examples | |
======== | |
>>> from sympy import minimal_polynomial, sqrt, Rational | |
>>> from sympy.abc import x | |
>>> minimal_polynomial(sqrt(2) + 3*Rational(1, 3), x, compose=False) | |
x**2 - 2*x - 1 | |
""" | |
generator = numbered_symbols('a', cls=Dummy) | |
mapping, symbols = {}, {} | |
def update_mapping(ex, exp, base=None): | |
a = next(generator) | |
symbols[ex] = a | |
if base is not None: | |
mapping[ex] = a**exp + base | |
else: | |
mapping[ex] = exp.as_expr(a) | |
return a | |
def bottom_up_scan(ex): | |
""" | |
Transform a given algebraic expression *ex* into a multivariate | |
polynomial, by introducing fresh variables with defining equations. | |
Explanation | |
=========== | |
The critical elements of the algebraic expression *ex* are root | |
extractions, instances of :py:class:`~.AlgebraicNumber`, and negative | |
powers. | |
When we encounter a root extraction or an :py:class:`~.AlgebraicNumber` | |
we replace this expression with a fresh variable ``a_i``, and record | |
the defining polynomial for ``a_i``. For example, if ``a_0**(1/3)`` | |
occurs, we will replace it with ``a_1``, and record the new defining | |
polynomial ``a_1**3 - a_0``. | |
When we encounter a negative power we transform it into a positive | |
power by algebraically inverting the base. This means computing the | |
minimal polynomial in ``x`` for the base, inverting ``x`` modulo this | |
poly (which generates a new polynomial) and then substituting the | |
original base expression for ``x`` in this last polynomial. | |
We return the transformed expression, and we record the defining | |
equations for new symbols using the ``update_mapping()`` function. | |
""" | |
if ex.is_Atom: | |
if ex is S.ImaginaryUnit: | |
if ex not in mapping: | |
return update_mapping(ex, 2, 1) | |
else: | |
return symbols[ex] | |
elif ex.is_Rational: | |
return ex | |
elif ex.is_Add: | |
return Add(*[ bottom_up_scan(g) for g in ex.args ]) | |
elif ex.is_Mul: | |
return Mul(*[ bottom_up_scan(g) for g in ex.args ]) | |
elif ex.is_Pow: | |
if ex.exp.is_Rational: | |
if ex.exp < 0: | |
minpoly_base = _minpoly_groebner(ex.base, x, cls) | |
inverse = invert(x, minpoly_base).as_expr() | |
base_inv = inverse.subs(x, ex.base).expand() | |
if ex.exp == -1: | |
return bottom_up_scan(base_inv) | |
else: | |
ex = base_inv**(-ex.exp) | |
if not ex.exp.is_Integer: | |
base, exp = ( | |
ex.base**ex.exp.p).expand(), Rational(1, ex.exp.q) | |
else: | |
base, exp = ex.base, ex.exp | |
base = bottom_up_scan(base) | |
expr = base**exp | |
if expr not in mapping: | |
if exp.is_Integer: | |
return expr.expand() | |
else: | |
return update_mapping(expr, 1 / exp, -base) | |
else: | |
return symbols[expr] | |
elif ex.is_AlgebraicNumber: | |
if ex not in mapping: | |
return update_mapping(ex, ex.minpoly_of_element()) | |
else: | |
return symbols[ex] | |
raise NotAlgebraic("%s does not seem to be an algebraic number" % ex) | |
def simpler_inverse(ex): | |
""" | |
Returns True if it is more likely that the minimal polynomial | |
algorithm works better with the inverse | |
""" | |
if ex.is_Pow: | |
if (1/ex.exp).is_integer and ex.exp < 0: | |
if ex.base.is_Add: | |
return True | |
if ex.is_Mul: | |
hit = True | |
for p in ex.args: | |
if p.is_Add: | |
return False | |
if p.is_Pow: | |
if p.base.is_Add and p.exp > 0: | |
return False | |
if hit: | |
return True | |
return False | |
inverted = False | |
ex = expand_multinomial(ex) | |
if ex.is_AlgebraicNumber: | |
return ex.minpoly_of_element().as_expr(x) | |
elif ex.is_Rational: | |
result = ex.q*x - ex.p | |
else: | |
inverted = simpler_inverse(ex) | |
if inverted: | |
ex = ex**-1 | |
res = None | |
if ex.is_Pow and (1/ex.exp).is_Integer: | |
n = 1/ex.exp | |
res = _minimal_polynomial_sq(ex.base, n, x) | |
elif _is_sum_surds(ex): | |
res = _minimal_polynomial_sq(ex, S.One, x) | |
if res is not None: | |
result = res | |
if res is None: | |
bus = bottom_up_scan(ex) | |
F = [x - bus] + list(mapping.values()) | |
G = groebner(F, list(symbols.values()) + [x], order='lex') | |
_, factors = factor_list(G[-1]) | |
# by construction G[-1] has root `ex` | |
result = _choose_factor(factors, x, ex) | |
if inverted: | |
result = _invertx(result, x) | |
if result.coeff(x**degree(result, x)) < 0: | |
result = expand_mul(-result) | |
return result | |
def minpoly(ex, x=None, compose=True, polys=False, domain=None): | |
"""This is a synonym for :py:func:`~.minimal_polynomial`.""" | |
return minimal_polynomial(ex, x=x, compose=compose, polys=polys, domain=domain) | |