Spaces:
Running
Running
"""Algorithms for computing symbolic roots of polynomials. """ | |
import math | |
from functools import reduce | |
from sympy.core import S, I, pi | |
from sympy.core.exprtools import factor_terms | |
from sympy.core.function import _mexpand | |
from sympy.core.logic import fuzzy_not | |
from sympy.core.mul import expand_2arg, Mul | |
from sympy.core.intfunc import igcd | |
from sympy.core.numbers import Rational, comp | |
from sympy.core.power import Pow | |
from sympy.core.relational import Eq | |
from sympy.core.sorting import ordered | |
from sympy.core.symbol import Dummy, Symbol, symbols | |
from sympy.core.sympify import sympify | |
from sympy.functions import exp, im, cos, acos, Piecewise | |
from sympy.functions.elementary.miscellaneous import root, sqrt | |
from sympy.ntheory import divisors, isprime, nextprime | |
from sympy.polys.domains import EX | |
from sympy.polys.polyerrors import (PolynomialError, GeneratorsNeeded, | |
DomainError, UnsolvableFactorError) | |
from sympy.polys.polyquinticconst import PolyQuintic | |
from sympy.polys.polytools import Poly, cancel, factor, gcd_list, discriminant | |
from sympy.polys.rationaltools import together | |
from sympy.polys.specialpolys import cyclotomic_poly | |
from sympy.utilities import public | |
from sympy.utilities.misc import filldedent | |
z = Symbol('z') # importing from abc cause O to be lost as clashing symbol | |
def roots_linear(f): | |
"""Returns a list of roots of a linear polynomial.""" | |
r = -f.nth(0)/f.nth(1) | |
dom = f.get_domain() | |
if not dom.is_Numerical: | |
if dom.is_Composite: | |
r = factor(r) | |
else: | |
from sympy.simplify.simplify import simplify | |
r = simplify(r) | |
return [r] | |
def roots_quadratic(f): | |
"""Returns a list of roots of a quadratic polynomial. If the domain is ZZ | |
then the roots will be sorted with negatives coming before positives. | |
The ordering will be the same for any numerical coefficients as long as | |
the assumptions tested are correct, otherwise the ordering will not be | |
sorted (but will be canonical). | |
""" | |
a, b, c = f.all_coeffs() | |
dom = f.get_domain() | |
def _sqrt(d): | |
# remove squares from square root since both will be represented | |
# in the results; a similar thing is happening in roots() but | |
# must be duplicated here because not all quadratics are binomials | |
co = [] | |
other = [] | |
for di in Mul.make_args(d): | |
if di.is_Pow and di.exp.is_Integer and di.exp % 2 == 0: | |
co.append(Pow(di.base, di.exp//2)) | |
else: | |
other.append(di) | |
if co: | |
d = Mul(*other) | |
co = Mul(*co) | |
return co*sqrt(d) | |
return sqrt(d) | |
def _simplify(expr): | |
if dom.is_Composite: | |
return factor(expr) | |
else: | |
from sympy.simplify.simplify import simplify | |
return simplify(expr) | |
if c is S.Zero: | |
r0, r1 = S.Zero, -b/a | |
if not dom.is_Numerical: | |
r1 = _simplify(r1) | |
elif r1.is_negative: | |
r0, r1 = r1, r0 | |
elif b is S.Zero: | |
r = -c/a | |
if not dom.is_Numerical: | |
r = _simplify(r) | |
R = _sqrt(r) | |
r0 = -R | |
r1 = R | |
else: | |
d = b**2 - 4*a*c | |
A = 2*a | |
B = -b/A | |
if not dom.is_Numerical: | |
d = _simplify(d) | |
B = _simplify(B) | |
D = factor_terms(_sqrt(d)/A) | |
r0 = B - D | |
r1 = B + D | |
if a.is_negative: | |
r0, r1 = r1, r0 | |
elif not dom.is_Numerical: | |
r0, r1 = [expand_2arg(i) for i in (r0, r1)] | |
return [r0, r1] | |
def roots_cubic(f, trig=False): | |
"""Returns a list of roots of a cubic polynomial. | |
References | |
========== | |
[1] https://en.wikipedia.org/wiki/Cubic_function, General formula for roots, | |
(accessed November 17, 2014). | |
""" | |
if trig: | |
a, b, c, d = f.all_coeffs() | |
p = (3*a*c - b**2)/(3*a**2) | |
q = (2*b**3 - 9*a*b*c + 27*a**2*d)/(27*a**3) | |
D = 18*a*b*c*d - 4*b**3*d + b**2*c**2 - 4*a*c**3 - 27*a**2*d**2 | |
if (D > 0) == True: | |
rv = [] | |
for k in range(3): | |
rv.append(2*sqrt(-p/3)*cos(acos(q/p*sqrt(-3/p)*Rational(3, 2))/3 - k*pi*Rational(2, 3))) | |
return [i - b/3/a for i in rv] | |
# a*x**3 + b*x**2 + c*x + d -> x**3 + a*x**2 + b*x + c | |
_, a, b, c = f.monic().all_coeffs() | |
if c is S.Zero: | |
x1, x2 = roots([1, a, b], multiple=True) | |
return [x1, S.Zero, x2] | |
# x**3 + a*x**2 + b*x + c -> u**3 + p*u + q | |
p = b - a**2/3 | |
q = c - a*b/3 + 2*a**3/27 | |
pon3 = p/3 | |
aon3 = a/3 | |
u1 = None | |
if p is S.Zero: | |
if q is S.Zero: | |
return [-aon3]*3 | |
u1 = -root(q, 3) if q.is_positive else root(-q, 3) | |
elif q is S.Zero: | |
y1, y2 = roots([1, 0, p], multiple=True) | |
return [tmp - aon3 for tmp in [y1, S.Zero, y2]] | |
elif q.is_real and q.is_negative: | |
u1 = -root(-q/2 + sqrt(q**2/4 + pon3**3), 3) | |
coeff = I*sqrt(3)/2 | |
if u1 is None: | |
u1 = S.One | |
u2 = Rational(-1, 2) + coeff | |
u3 = Rational(-1, 2) - coeff | |
b, c, d = a, b, c # a, b, c, d = S.One, a, b, c | |
D0 = b**2 - 3*c # b**2 - 3*a*c | |
D1 = 2*b**3 - 9*b*c + 27*d # 2*b**3 - 9*a*b*c + 27*a**2*d | |
C = root((D1 + sqrt(D1**2 - 4*D0**3))/2, 3) | |
return [-(b + uk*C + D0/C/uk)/3 for uk in [u1, u2, u3]] # -(b + uk*C + D0/C/uk)/3/a | |
u2 = u1*(Rational(-1, 2) + coeff) | |
u3 = u1*(Rational(-1, 2) - coeff) | |
if p is S.Zero: | |
return [u1 - aon3, u2 - aon3, u3 - aon3] | |
soln = [ | |
-u1 + pon3/u1 - aon3, | |
-u2 + pon3/u2 - aon3, | |
-u3 + pon3/u3 - aon3 | |
] | |
return soln | |
def _roots_quartic_euler(p, q, r, a): | |
""" | |
Descartes-Euler solution of the quartic equation | |
Parameters | |
========== | |
p, q, r: coefficients of ``x**4 + p*x**2 + q*x + r`` | |
a: shift of the roots | |
Notes | |
===== | |
This is a helper function for ``roots_quartic``. | |
Look for solutions of the form :: | |
``x1 = sqrt(R) - sqrt(A + B*sqrt(R))`` | |
``x2 = -sqrt(R) - sqrt(A - B*sqrt(R))`` | |
``x3 = -sqrt(R) + sqrt(A - B*sqrt(R))`` | |
``x4 = sqrt(R) + sqrt(A + B*sqrt(R))`` | |
To satisfy the quartic equation one must have | |
``p = -2*(R + A); q = -4*B*R; r = (R - A)**2 - B**2*R`` | |
so that ``R`` must satisfy the Descartes-Euler resolvent equation | |
``64*R**3 + 32*p*R**2 + (4*p**2 - 16*r)*R - q**2 = 0`` | |
If the resolvent does not have a rational solution, return None; | |
in that case it is likely that the Ferrari method gives a simpler | |
solution. | |
Examples | |
======== | |
>>> from sympy import S | |
>>> from sympy.polys.polyroots import _roots_quartic_euler | |
>>> p, q, r = -S(64)/5, -S(512)/125, -S(1024)/3125 | |
>>> _roots_quartic_euler(p, q, r, S(0))[0] | |
-sqrt(32*sqrt(5)/125 + 16/5) + 4*sqrt(5)/5 | |
""" | |
# solve the resolvent equation | |
x = Dummy('x') | |
eq = 64*x**3 + 32*p*x**2 + (4*p**2 - 16*r)*x - q**2 | |
xsols = list(roots(Poly(eq, x), cubics=False).keys()) | |
xsols = [sol for sol in xsols if sol.is_rational and sol.is_nonzero] | |
if not xsols: | |
return None | |
R = max(xsols) | |
c1 = sqrt(R) | |
B = -q*c1/(4*R) | |
A = -R - p/2 | |
c2 = sqrt(A + B) | |
c3 = sqrt(A - B) | |
return [c1 - c2 - a, -c1 - c3 - a, -c1 + c3 - a, c1 + c2 - a] | |
def roots_quartic(f): | |
r""" | |
Returns a list of roots of a quartic polynomial. | |
There are many references for solving quartic expressions available [1-5]. | |
This reviewer has found that many of them require one to select from among | |
2 or more possible sets of solutions and that some solutions work when one | |
is searching for real roots but do not work when searching for complex roots | |
(though this is not always stated clearly). The following routine has been | |
tested and found to be correct for 0, 2 or 4 complex roots. | |
The quasisymmetric case solution [6] looks for quartics that have the form | |
`x**4 + A*x**3 + B*x**2 + C*x + D = 0` where `(C/A)**2 = D`. | |
Although no general solution that is always applicable for all | |
coefficients is known to this reviewer, certain conditions are tested | |
to determine the simplest 4 expressions that can be returned: | |
1) `f = c + a*(a**2/8 - b/2) == 0` | |
2) `g = d - a*(a*(3*a**2/256 - b/16) + c/4) = 0` | |
3) if `f != 0` and `g != 0` and `p = -d + a*c/4 - b**2/12` then | |
a) `p == 0` | |
b) `p != 0` | |
Examples | |
======== | |
>>> from sympy import Poly | |
>>> from sympy.polys.polyroots import roots_quartic | |
>>> r = roots_quartic(Poly('x**4-6*x**3+17*x**2-26*x+20')) | |
>>> # 4 complex roots: 1+-I*sqrt(3), 2+-I | |
>>> sorted(str(tmp.evalf(n=2)) for tmp in r) | |
['1.0 + 1.7*I', '1.0 - 1.7*I', '2.0 + 1.0*I', '2.0 - 1.0*I'] | |
References | |
========== | |
1. http://mathforum.org/dr.math/faq/faq.cubic.equations.html | |
2. https://en.wikipedia.org/wiki/Quartic_function#Summary_of_Ferrari.27s_method | |
3. https://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html | |
4. https://people.bath.ac.uk/masjhd/JHD-CA.pdf | |
5. http://www.albmath.org/files/Math_5713.pdf | |
6. https://web.archive.org/web/20171002081448/http://www.statemaster.com/encyclopedia/Quartic-equation | |
7. https://eqworld.ipmnet.ru/en/solutions/ae/ae0108.pdf | |
""" | |
_, a, b, c, d = f.monic().all_coeffs() | |
if not d: | |
return [S.Zero] + roots([1, a, b, c], multiple=True) | |
elif (c/a)**2 == d: | |
x, m = f.gen, c/a | |
g = Poly(x**2 + a*x + b - 2*m, x) | |
z1, z2 = roots_quadratic(g) | |
h1 = Poly(x**2 - z1*x + m, x) | |
h2 = Poly(x**2 - z2*x + m, x) | |
r1 = roots_quadratic(h1) | |
r2 = roots_quadratic(h2) | |
return r1 + r2 | |
else: | |
a2 = a**2 | |
e = b - 3*a2/8 | |
f = _mexpand(c + a*(a2/8 - b/2)) | |
aon4 = a/4 | |
g = _mexpand(d - aon4*(a*(3*a2/64 - b/4) + c)) | |
if f.is_zero: | |
y1, y2 = [sqrt(tmp) for tmp in | |
roots([1, e, g], multiple=True)] | |
return [tmp - aon4 for tmp in [-y1, -y2, y1, y2]] | |
if g.is_zero: | |
y = [S.Zero] + roots([1, 0, e, f], multiple=True) | |
return [tmp - aon4 for tmp in y] | |
else: | |
# Descartes-Euler method, see [7] | |
sols = _roots_quartic_euler(e, f, g, aon4) | |
if sols: | |
return sols | |
# Ferrari method, see [1, 2] | |
p = -e**2/12 - g | |
q = -e**3/108 + e*g/3 - f**2/8 | |
TH = Rational(1, 3) | |
def _ans(y): | |
w = sqrt(e + 2*y) | |
arg1 = 3*e + 2*y | |
arg2 = 2*f/w | |
ans = [] | |
for s in [-1, 1]: | |
root = sqrt(-(arg1 + s*arg2)) | |
for t in [-1, 1]: | |
ans.append((s*w - t*root)/2 - aon4) | |
return ans | |
# whether a Piecewise is returned or not | |
# depends on knowing p, so try to put | |
# in a simple form | |
p = _mexpand(p) | |
# p == 0 case | |
y1 = e*Rational(-5, 6) - q**TH | |
if p.is_zero: | |
return _ans(y1) | |
# if p != 0 then u below is not 0 | |
root = sqrt(q**2/4 + p**3/27) | |
r = -q/2 + root # or -q/2 - root | |
u = r**TH # primary root of solve(x**3 - r, x) | |
y2 = e*Rational(-5, 6) + u - p/u/3 | |
if fuzzy_not(p.is_zero): | |
return _ans(y2) | |
# sort it out once they know the values of the coefficients | |
return [Piecewise((a1, Eq(p, 0)), (a2, True)) | |
for a1, a2 in zip(_ans(y1), _ans(y2))] | |
def roots_binomial(f): | |
"""Returns a list of roots of a binomial polynomial. If the domain is ZZ | |
then the roots will be sorted with negatives coming before positives. | |
The ordering will be the same for any numerical coefficients as long as | |
the assumptions tested are correct, otherwise the ordering will not be | |
sorted (but will be canonical). | |
""" | |
n = f.degree() | |
a, b = f.nth(n), f.nth(0) | |
base = -cancel(b/a) | |
alpha = root(base, n) | |
if alpha.is_number: | |
alpha = alpha.expand(complex=True) | |
# define some parameters that will allow us to order the roots. | |
# If the domain is ZZ this is guaranteed to return roots sorted | |
# with reals before non-real roots and non-real sorted according | |
# to real part and imaginary part, e.g. -1, 1, -1 + I, 2 - I | |
neg = base.is_negative | |
even = n % 2 == 0 | |
if neg: | |
if even == True and (base + 1).is_positive: | |
big = True | |
else: | |
big = False | |
# get the indices in the right order so the computed | |
# roots will be sorted when the domain is ZZ | |
ks = [] | |
imax = n//2 | |
if even: | |
ks.append(imax) | |
imax -= 1 | |
if not neg: | |
ks.append(0) | |
for i in range(imax, 0, -1): | |
if neg: | |
ks.extend([i, -i]) | |
else: | |
ks.extend([-i, i]) | |
if neg: | |
ks.append(0) | |
if big: | |
for i in range(0, len(ks), 2): | |
pair = ks[i: i + 2] | |
pair = list(reversed(pair)) | |
# compute the roots | |
roots, d = [], 2*I*pi/n | |
for k in ks: | |
zeta = exp(k*d).expand(complex=True) | |
roots.append((alpha*zeta).expand(power_base=False)) | |
return roots | |
def _inv_totient_estimate(m): | |
""" | |
Find ``(L, U)`` such that ``L <= phi^-1(m) <= U``. | |
Examples | |
======== | |
>>> from sympy.polys.polyroots import _inv_totient_estimate | |
>>> _inv_totient_estimate(192) | |
(192, 840) | |
>>> _inv_totient_estimate(400) | |
(400, 1750) | |
""" | |
primes = [ d + 1 for d in divisors(m) if isprime(d + 1) ] | |
a, b = 1, 1 | |
for p in primes: | |
a *= p | |
b *= p - 1 | |
L = m | |
U = int(math.ceil(m*(float(a)/b))) | |
P = p = 2 | |
primes = [] | |
while P <= U: | |
p = nextprime(p) | |
primes.append(p) | |
P *= p | |
P //= p | |
b = 1 | |
for p in primes[:-1]: | |
b *= p - 1 | |
U = int(math.ceil(m*(float(P)/b))) | |
return L, U | |
def roots_cyclotomic(f, factor=False): | |
"""Compute roots of cyclotomic polynomials. """ | |
L, U = _inv_totient_estimate(f.degree()) | |
for n in range(L, U + 1): | |
g = cyclotomic_poly(n, f.gen, polys=True) | |
if f.expr == g.expr: | |
break | |
else: # pragma: no cover | |
raise RuntimeError("failed to find index of a cyclotomic polynomial") | |
roots = [] | |
if not factor: | |
# get the indices in the right order so the computed | |
# roots will be sorted | |
h = n//2 | |
ks = [i for i in range(1, n + 1) if igcd(i, n) == 1] | |
ks.sort(key=lambda x: (x, -1) if x <= h else (abs(x - n), 1)) | |
d = 2*I*pi/n | |
for k in reversed(ks): | |
roots.append(exp(k*d).expand(complex=True)) | |
else: | |
g = Poly(f, extension=root(-1, n)) | |
for h, _ in ordered(g.factor_list()[1]): | |
roots.append(-h.TC()) | |
return roots | |
def roots_quintic(f): | |
""" | |
Calculate exact roots of a solvable irreducible quintic with rational coefficients. | |
Return an empty list if the quintic is reducible or not solvable. | |
""" | |
result = [] | |
coeff_5, coeff_4, p_, q_, r_, s_ = f.all_coeffs() | |
if not all(coeff.is_Rational for coeff in (coeff_5, coeff_4, p_, q_, r_, s_)): | |
return result | |
if coeff_5 != 1: | |
f = Poly(f / coeff_5) | |
_, coeff_4, p_, q_, r_, s_ = f.all_coeffs() | |
# Cancel coeff_4 to form x^5 + px^3 + qx^2 + rx + s | |
if coeff_4: | |
p = p_ - 2*coeff_4*coeff_4/5 | |
q = q_ - 3*coeff_4*p_/5 + 4*coeff_4**3/25 | |
r = r_ - 2*coeff_4*q_/5 + 3*coeff_4**2*p_/25 - 3*coeff_4**4/125 | |
s = s_ - coeff_4*r_/5 + coeff_4**2*q_/25 - coeff_4**3*p_/125 + 4*coeff_4**5/3125 | |
x = f.gen | |
f = Poly(x**5 + p*x**3 + q*x**2 + r*x + s) | |
else: | |
p, q, r, s = p_, q_, r_, s_ | |
quintic = PolyQuintic(f) | |
# Eqn standardized. Algo for solving starts here | |
if not f.is_irreducible: | |
return result | |
f20 = quintic.f20 | |
# Check if f20 has linear factors over domain Z | |
if f20.is_irreducible: | |
return result | |
# Now, we know that f is solvable | |
for _factor in f20.factor_list()[1]: | |
if _factor[0].is_linear: | |
theta = _factor[0].root(0) | |
break | |
d = discriminant(f) | |
delta = sqrt(d) | |
# zeta = a fifth root of unity | |
zeta1, zeta2, zeta3, zeta4 = quintic.zeta | |
T = quintic.T(theta, d) | |
tol = S(1e-10) | |
alpha = T[1] + T[2]*delta | |
alpha_bar = T[1] - T[2]*delta | |
beta = T[3] + T[4]*delta | |
beta_bar = T[3] - T[4]*delta | |
disc = alpha**2 - 4*beta | |
disc_bar = alpha_bar**2 - 4*beta_bar | |
l0 = quintic.l0(theta) | |
Stwo = S(2) | |
l1 = _quintic_simplify((-alpha + sqrt(disc)) / Stwo) | |
l4 = _quintic_simplify((-alpha - sqrt(disc)) / Stwo) | |
l2 = _quintic_simplify((-alpha_bar + sqrt(disc_bar)) / Stwo) | |
l3 = _quintic_simplify((-alpha_bar - sqrt(disc_bar)) / Stwo) | |
order = quintic.order(theta, d) | |
test = (order*delta.n()) - ( (l1.n() - l4.n())*(l2.n() - l3.n()) ) | |
# Comparing floats | |
if not comp(test, 0, tol): | |
l2, l3 = l3, l2 | |
# Now we have correct order of l's | |
R1 = l0 + l1*zeta1 + l2*zeta2 + l3*zeta3 + l4*zeta4 | |
R2 = l0 + l3*zeta1 + l1*zeta2 + l4*zeta3 + l2*zeta4 | |
R3 = l0 + l2*zeta1 + l4*zeta2 + l1*zeta3 + l3*zeta4 | |
R4 = l0 + l4*zeta1 + l3*zeta2 + l2*zeta3 + l1*zeta4 | |
Res = [None, [None]*5, [None]*5, [None]*5, [None]*5] | |
Res_n = [None, [None]*5, [None]*5, [None]*5, [None]*5] | |
# Simplifying improves performance a lot for exact expressions | |
R1 = _quintic_simplify(R1) | |
R2 = _quintic_simplify(R2) | |
R3 = _quintic_simplify(R3) | |
R4 = _quintic_simplify(R4) | |
# hard-coded results for [factor(i) for i in _vsolve(x**5 - a - I*b, x)] | |
x0 = z**(S(1)/5) | |
x1 = sqrt(2) | |
x2 = sqrt(5) | |
x3 = sqrt(5 - x2) | |
x4 = I*x2 | |
x5 = x4 + I | |
x6 = I*x0/4 | |
x7 = x1*sqrt(x2 + 5) | |
sol = [x0, -x6*(x1*x3 - x5), x6*(x1*x3 + x5), -x6*(x4 + x7 - I), x6*(-x4 + x7 + I)] | |
R1 = R1.as_real_imag() | |
R2 = R2.as_real_imag() | |
R3 = R3.as_real_imag() | |
R4 = R4.as_real_imag() | |
for i, s in enumerate(sol): | |
Res[1][i] = _quintic_simplify(s.xreplace({z: R1[0] + I*R1[1]})) | |
Res[2][i] = _quintic_simplify(s.xreplace({z: R2[0] + I*R2[1]})) | |
Res[3][i] = _quintic_simplify(s.xreplace({z: R3[0] + I*R3[1]})) | |
Res[4][i] = _quintic_simplify(s.xreplace({z: R4[0] + I*R4[1]})) | |
for i in range(1, 5): | |
for j in range(5): | |
Res_n[i][j] = Res[i][j].n() | |
Res[i][j] = _quintic_simplify(Res[i][j]) | |
r1 = Res[1][0] | |
r1_n = Res_n[1][0] | |
for i in range(5): | |
if comp(im(r1_n*Res_n[4][i]), 0, tol): | |
r4 = Res[4][i] | |
break | |
# Now we have various Res values. Each will be a list of five | |
# values. We have to pick one r value from those five for each Res | |
u, v = quintic.uv(theta, d) | |
testplus = (u + v*delta*sqrt(5)).n() | |
testminus = (u - v*delta*sqrt(5)).n() | |
# Evaluated numbers suffixed with _n | |
# We will use evaluated numbers for calculation. Much faster. | |
r4_n = r4.n() | |
r2 = r3 = None | |
for i in range(5): | |
r2temp_n = Res_n[2][i] | |
for j in range(5): | |
# Again storing away the exact number and using | |
# evaluated numbers in computations | |
r3temp_n = Res_n[3][j] | |
if (comp((r1_n*r2temp_n**2 + r4_n*r3temp_n**2 - testplus).n(), 0, tol) and | |
comp((r3temp_n*r1_n**2 + r2temp_n*r4_n**2 - testminus).n(), 0, tol)): | |
r2 = Res[2][i] | |
r3 = Res[3][j] | |
break | |
if r2 is not None: | |
break | |
else: | |
return [] # fall back to normal solve | |
# Now, we have r's so we can get roots | |
x1 = (r1 + r2 + r3 + r4)/5 | |
x2 = (r1*zeta4 + r2*zeta3 + r3*zeta2 + r4*zeta1)/5 | |
x3 = (r1*zeta3 + r2*zeta1 + r3*zeta4 + r4*zeta2)/5 | |
x4 = (r1*zeta2 + r2*zeta4 + r3*zeta1 + r4*zeta3)/5 | |
x5 = (r1*zeta1 + r2*zeta2 + r3*zeta3 + r4*zeta4)/5 | |
result = [x1, x2, x3, x4, x5] | |
# Now check if solutions are distinct | |
saw = set() | |
for r in result: | |
r = r.n(2) | |
if r in saw: | |
# Roots were identical. Abort, return [] | |
# and fall back to usual solve | |
return [] | |
saw.add(r) | |
# Restore to original equation where coeff_4 is nonzero | |
if coeff_4: | |
result = [x - coeff_4 / 5 for x in result] | |
return result | |
def _quintic_simplify(expr): | |
from sympy.simplify.simplify import powsimp | |
expr = powsimp(expr) | |
expr = cancel(expr) | |
return together(expr) | |
def _integer_basis(poly): | |
"""Compute coefficient basis for a polynomial over integers. | |
Returns the integer ``div`` such that substituting ``x = div*y`` | |
``p(x) = m*q(y)`` where the coefficients of ``q`` are smaller | |
than those of ``p``. | |
For example ``x**5 + 512*x + 1024 = 0`` | |
with ``div = 4`` becomes ``y**5 + 2*y + 1 = 0`` | |
Returns the integer ``div`` or ``None`` if there is no possible scaling. | |
Examples | |
======== | |
>>> from sympy.polys import Poly | |
>>> from sympy.abc import x | |
>>> from sympy.polys.polyroots import _integer_basis | |
>>> p = Poly(x**5 + 512*x + 1024, x, domain='ZZ') | |
>>> _integer_basis(p) | |
4 | |
""" | |
monoms, coeffs = list(zip(*poly.terms())) | |
monoms, = list(zip(*monoms)) | |
coeffs = list(map(abs, coeffs)) | |
if coeffs[0] < coeffs[-1]: | |
coeffs = list(reversed(coeffs)) | |
n = monoms[0] | |
monoms = [n - i for i in reversed(monoms)] | |
else: | |
return None | |
monoms = monoms[:-1] | |
coeffs = coeffs[:-1] | |
# Special case for two-term polynominals | |
if len(monoms) == 1: | |
r = Pow(coeffs[0], S.One/monoms[0]) | |
if r.is_Integer: | |
return int(r) | |
else: | |
return None | |
divs = reversed(divisors(gcd_list(coeffs))[1:]) | |
try: | |
div = next(divs) | |
except StopIteration: | |
return None | |
while True: | |
for monom, coeff in zip(monoms, coeffs): | |
if coeff % div**monom != 0: | |
try: | |
div = next(divs) | |
except StopIteration: | |
return None | |
else: | |
break | |
else: | |
return div | |
def preprocess_roots(poly): | |
"""Try to get rid of symbolic coefficients from ``poly``. """ | |
coeff = S.One | |
poly_func = poly.func | |
try: | |
_, poly = poly.clear_denoms(convert=True) | |
except DomainError: | |
return coeff, poly | |
poly = poly.primitive()[1] | |
poly = poly.retract() | |
# TODO: This is fragile. Figure out how to make this independent of construct_domain(). | |
if poly.get_domain().is_Poly and all(c.is_term for c in poly.rep.coeffs()): | |
poly = poly.inject() | |
strips = list(zip(*poly.monoms())) | |
gens = list(poly.gens[1:]) | |
base, strips = strips[0], strips[1:] | |
for gen, strip in zip(list(gens), strips): | |
reverse = False | |
if strip[0] < strip[-1]: | |
strip = reversed(strip) | |
reverse = True | |
ratio = None | |
for a, b in zip(base, strip): | |
if not a and not b: | |
continue | |
elif not a or not b: | |
break | |
elif b % a != 0: | |
break | |
else: | |
_ratio = b // a | |
if ratio is None: | |
ratio = _ratio | |
elif ratio != _ratio: | |
break | |
else: | |
if reverse: | |
ratio = -ratio | |
poly = poly.eval(gen, 1) | |
coeff *= gen**(-ratio) | |
gens.remove(gen) | |
if gens: | |
poly = poly.eject(*gens) | |
if poly.is_univariate and poly.get_domain().is_ZZ: | |
basis = _integer_basis(poly) | |
if basis is not None: | |
n = poly.degree() | |
def func(k, coeff): | |
return coeff//basis**(n - k[0]) | |
poly = poly.termwise(func) | |
coeff *= basis | |
if not isinstance(poly, poly_func): | |
poly = poly_func(poly) | |
return coeff, poly | |
def roots(f, *gens, | |
auto=True, | |
cubics=True, | |
trig=False, | |
quartics=True, | |
quintics=False, | |
multiple=False, | |
filter=None, | |
predicate=None, | |
strict=False, | |
**flags): | |
""" | |
Computes symbolic roots of a univariate polynomial. | |
Given a univariate polynomial f with symbolic coefficients (or | |
a list of the polynomial's coefficients), returns a dictionary | |
with its roots and their multiplicities. | |
Only roots expressible via radicals will be returned. To get | |
a complete set of roots use RootOf class or numerical methods | |
instead. By default cubic and quartic formulas are used in | |
the algorithm. To disable them because of unreadable output | |
set ``cubics=False`` or ``quartics=False`` respectively. If cubic | |
roots are real but are expressed in terms of complex numbers | |
(casus irreducibilis [1]) the ``trig`` flag can be set to True to | |
have the solutions returned in terms of cosine and inverse cosine | |
functions. | |
To get roots from a specific domain set the ``filter`` flag with | |
one of the following specifiers: Z, Q, R, I, C. By default all | |
roots are returned (this is equivalent to setting ``filter='C'``). | |
By default a dictionary is returned giving a compact result in | |
case of multiple roots. However to get a list containing all | |
those roots set the ``multiple`` flag to True; the list will | |
have identical roots appearing next to each other in the result. | |
(For a given Poly, the all_roots method will give the roots in | |
sorted numerical order.) | |
If the ``strict`` flag is True, ``UnsolvableFactorError`` will be | |
raised if the roots found are known to be incomplete (because | |
some roots are not expressible in radicals). | |
Examples | |
======== | |
>>> from sympy import Poly, roots, degree | |
>>> from sympy.abc import x, y | |
>>> roots(x**2 - 1, x) | |
{-1: 1, 1: 1} | |
>>> p = Poly(x**2-1, x) | |
>>> roots(p) | |
{-1: 1, 1: 1} | |
>>> p = Poly(x**2-y, x, y) | |
>>> roots(Poly(p, x)) | |
{-sqrt(y): 1, sqrt(y): 1} | |
>>> roots(x**2 - y, x) | |
{-sqrt(y): 1, sqrt(y): 1} | |
>>> roots([1, 0, -1]) | |
{-1: 1, 1: 1} | |
``roots`` will only return roots expressible in radicals. If | |
the given polynomial has some or all of its roots inexpressible in | |
radicals, the result of ``roots`` will be incomplete or empty | |
respectively. | |
Example where result is incomplete: | |
>>> roots((x-1)*(x**5-x+1), x) | |
{1: 1} | |
In this case, the polynomial has an unsolvable quintic factor | |
whose roots cannot be expressed by radicals. The polynomial has a | |
rational root (due to the factor `(x-1)`), which is returned since | |
``roots`` always finds all rational roots. | |
Example where result is empty: | |
>>> roots(x**7-3*x**2+1, x) | |
{} | |
Here, the polynomial has no roots expressible in radicals, so | |
``roots`` returns an empty dictionary. | |
The result produced by ``roots`` is complete if and only if the | |
sum of the multiplicity of each root is equal to the degree of | |
the polynomial. If strict=True, UnsolvableFactorError will be | |
raised if the result is incomplete. | |
The result can be be checked for completeness as follows: | |
>>> f = x**3-2*x**2+1 | |
>>> sum(roots(f, x).values()) == degree(f, x) | |
True | |
>>> f = (x-1)*(x**5-x+1) | |
>>> sum(roots(f, x).values()) == degree(f, x) | |
False | |
References | |
========== | |
.. [1] https://en.wikipedia.org/wiki/Cubic_equation#Trigonometric_and_hyperbolic_solutions | |
""" | |
from sympy.polys.polytools import to_rational_coeffs | |
flags = dict(flags) | |
if isinstance(f, list): | |
if gens: | |
raise ValueError('redundant generators given') | |
x = Dummy('x') | |
poly, i = {}, len(f) - 1 | |
for coeff in f: | |
poly[i], i = sympify(coeff), i - 1 | |
f = Poly(poly, x, field=True) | |
else: | |
try: | |
F = Poly(f, *gens, **flags) | |
if not isinstance(f, Poly) and not F.gen.is_Symbol: | |
raise PolynomialError("generator must be a Symbol") | |
f = F | |
except GeneratorsNeeded: | |
if multiple: | |
return [] | |
else: | |
return {} | |
else: | |
n = f.degree() | |
if f.length() == 2 and n > 2: | |
# check for foo**n in constant if dep is c*gen**m | |
con, dep = f.as_expr().as_independent(*f.gens) | |
fcon = -(-con).factor() | |
if fcon != con: | |
con = fcon | |
bases = [] | |
for i in Mul.make_args(con): | |
if i.is_Pow: | |
b, e = i.as_base_exp() | |
if e.is_Integer and b.is_Add: | |
bases.append((b, Dummy(positive=True))) | |
if bases: | |
rv = roots(Poly((dep + con).xreplace(dict(bases)), | |
*f.gens), *F.gens, | |
auto=auto, | |
cubics=cubics, | |
trig=trig, | |
quartics=quartics, | |
quintics=quintics, | |
multiple=multiple, | |
filter=filter, | |
predicate=predicate, | |
**flags) | |
return {factor_terms(k.xreplace( | |
{v: k for k, v in bases}) | |
): v for k, v in rv.items()} | |
if f.is_multivariate: | |
raise PolynomialError('multivariate polynomials are not supported') | |
def _update_dict(result, zeros, currentroot, k): | |
if currentroot == S.Zero: | |
if S.Zero in zeros: | |
zeros[S.Zero] += k | |
else: | |
zeros[S.Zero] = k | |
if currentroot in result: | |
result[currentroot] += k | |
else: | |
result[currentroot] = k | |
def _try_decompose(f): | |
"""Find roots using functional decomposition. """ | |
factors, roots = f.decompose(), [] | |
for currentroot in _try_heuristics(factors[0]): | |
roots.append(currentroot) | |
for currentfactor in factors[1:]: | |
previous, roots = list(roots), [] | |
for currentroot in previous: | |
g = currentfactor - Poly(currentroot, f.gen) | |
for currentroot in _try_heuristics(g): | |
roots.append(currentroot) | |
return roots | |
def _try_heuristics(f): | |
"""Find roots using formulas and some tricks. """ | |
if f.is_ground: | |
return [] | |
if f.is_monomial: | |
return [S.Zero]*f.degree() | |
if f.length() == 2: | |
if f.degree() == 1: | |
return list(map(cancel, roots_linear(f))) | |
else: | |
return roots_binomial(f) | |
result = [] | |
for i in [-1, 1]: | |
if not f.eval(i): | |
f = f.quo(Poly(f.gen - i, f.gen)) | |
result.append(i) | |
break | |
n = f.degree() | |
if n == 1: | |
result += list(map(cancel, roots_linear(f))) | |
elif n == 2: | |
result += list(map(cancel, roots_quadratic(f))) | |
elif f.is_cyclotomic: | |
result += roots_cyclotomic(f) | |
elif n == 3 and cubics: | |
result += roots_cubic(f, trig=trig) | |
elif n == 4 and quartics: | |
result += roots_quartic(f) | |
elif n == 5 and quintics: | |
result += roots_quintic(f) | |
return result | |
# Convert the generators to symbols | |
dumgens = symbols('x:%d' % len(f.gens), cls=Dummy) | |
f = f.per(f.rep, dumgens) | |
(k,), f = f.terms_gcd() | |
if not k: | |
zeros = {} | |
else: | |
zeros = {S.Zero: k} | |
coeff, f = preprocess_roots(f) | |
if auto and f.get_domain().is_Ring: | |
f = f.to_field() | |
# Use EX instead of ZZ_I or QQ_I | |
if f.get_domain().is_QQ_I: | |
f = f.per(f.rep.convert(EX)) | |
rescale_x = None | |
translate_x = None | |
result = {} | |
if not f.is_ground: | |
dom = f.get_domain() | |
if not dom.is_Exact and dom.is_Numerical: | |
for r in f.nroots(): | |
_update_dict(result, zeros, r, 1) | |
elif f.degree() == 1: | |
_update_dict(result, zeros, roots_linear(f)[0], 1) | |
elif f.length() == 2: | |
roots_fun = roots_quadratic if f.degree() == 2 else roots_binomial | |
for r in roots_fun(f): | |
_update_dict(result, zeros, r, 1) | |
else: | |
_, factors = Poly(f.as_expr()).factor_list() | |
if len(factors) == 1 and f.degree() == 2: | |
for r in roots_quadratic(f): | |
_update_dict(result, zeros, r, 1) | |
else: | |
if len(factors) == 1 and factors[0][1] == 1: | |
if f.get_domain().is_EX: | |
res = to_rational_coeffs(f) | |
if res: | |
if res[0] is None: | |
translate_x, f = res[2:] | |
else: | |
rescale_x, f = res[1], res[-1] | |
result = roots(f) | |
if not result: | |
for currentroot in _try_decompose(f): | |
_update_dict(result, zeros, currentroot, 1) | |
else: | |
for r in _try_heuristics(f): | |
_update_dict(result, zeros, r, 1) | |
else: | |
for currentroot in _try_decompose(f): | |
_update_dict(result, zeros, currentroot, 1) | |
else: | |
for currentfactor, k in factors: | |
for r in _try_heuristics(Poly(currentfactor, f.gen, field=True)): | |
_update_dict(result, zeros, r, k) | |
if coeff is not S.One: | |
_result, result, = result, {} | |
for currentroot, k in _result.items(): | |
result[coeff*currentroot] = k | |
if filter not in [None, 'C']: | |
handlers = { | |
'Z': lambda r: r.is_Integer, | |
'Q': lambda r: r.is_Rational, | |
'R': lambda r: all(a.is_real for a in r.as_numer_denom()), | |
'I': lambda r: r.is_imaginary, | |
} | |
try: | |
query = handlers[filter] | |
except KeyError: | |
raise ValueError("Invalid filter: %s" % filter) | |
for zero in dict(result).keys(): | |
if not query(zero): | |
del result[zero] | |
if predicate is not None: | |
for zero in dict(result).keys(): | |
if not predicate(zero): | |
del result[zero] | |
if rescale_x: | |
result1 = {} | |
for k, v in result.items(): | |
result1[k*rescale_x] = v | |
result = result1 | |
if translate_x: | |
result1 = {} | |
for k, v in result.items(): | |
result1[k + translate_x] = v | |
result = result1 | |
# adding zero roots after non-trivial roots have been translated | |
result.update(zeros) | |
if strict and sum(result.values()) < f.degree(): | |
raise UnsolvableFactorError(filldedent(''' | |
Strict mode: some factors cannot be solved in radicals, so | |
a complete list of solutions cannot be returned. Call | |
roots with strict=False to get solutions expressible in | |
radicals (if there are any). | |
''')) | |
if not multiple: | |
return result | |
else: | |
zeros = [] | |
for zero in ordered(result): | |
zeros.extend([zero]*result[zero]) | |
return zeros | |
def root_factors(f, *gens, filter=None, **args): | |
""" | |
Returns all factors of a univariate polynomial. | |
Examples | |
======== | |
>>> from sympy.abc import x, y | |
>>> from sympy.polys.polyroots import root_factors | |
>>> root_factors(x**2 - y, x) | |
[x - sqrt(y), x + sqrt(y)] | |
""" | |
args = dict(args) | |
F = Poly(f, *gens, **args) | |
if not F.is_Poly: | |
return [f] | |
if F.is_multivariate: | |
raise ValueError('multivariate polynomials are not supported') | |
x = F.gens[0] | |
zeros = roots(F, filter=filter) | |
if not zeros: | |
factors = [F] | |
else: | |
factors, N = [], 0 | |
for r, n in ordered(zeros.items()): | |
factors, N = factors + [Poly(x - r, x)]*n, N + n | |
if N < F.degree(): | |
G = reduce(lambda p, q: p*q, factors) | |
factors.append(F.quo(G)) | |
if not isinstance(f, Poly): | |
factors = [ f.as_expr() for f in factors ] | |
return factors | |