In this section, we’ll work through the procedure for computing the roots of a quintic in the case of a specific example with known roots, three real and two complex, of the polynomial given by
Since the roots are known, we can run the polynomial coefficients through our algorithm and verify that the appropriate roots are found.
The first step is to perform a Tschirnhaus transformation, in order to get the coefficients of the corresponding principal quintic. Taking
with , , we find
and so , , and .
# Check of the above:
from sympy import sqrt, symbols, resultant, simplify
p1, p2, p3, p4, p5 = symbols("p_1 p_2 p_3 p_4 p_5")
x, y = symbols("x y")
P = x**5 + p1 * x**4 + p2 * x**3 + p3 * x**2 + p4 * x + p5
p_vals = {p1: 3, p2: 6, p3: 7, p4: -51, p5: -54}
B = (
p1**2 * p2 / 2
+ 3 * p1 * p3 / 2
+ p1
* sqrt(
40 * p1**3 * p3
- 15 * p1**2 * p2**2
+ 80 * p1**2 * p4
- 190 * p1 * p2 * p3
+ 60 * p2**3
- 200 * p2 * p4
+ 225 * p3**2
)
/ 10
- 2 * p2**2
) / (2 * p1**2 - 5 * p2)
B_val = B.subs(p_vals)
B_valA = (5 * B + p1**2 - 2 * p2) / p1
A_val = A.subs(p_vals)
A_valQ = resultant(P, y - x**2 - A_val * x - B_val, x).subs(p_vals)
Qalpha, beta, gamma = symbols("alpha beta gamma")
q_vals = {alpha: Q.coeff(y, 2) / 5, beta: Q.coeff(y, 1) / 5, gamma: Q.coeff(y, 0)}
q_vals{alpha: -2213, beta: -17652, gamma: 529056}Now that the coefficients of the principal quintic have been identified, we can proceed by solving for the values appearing in the expression
First of all, is found by solving the quadratic equation
Solving for , given the values of above, we find
# Check of the above:
l, mu = symbols("lambda mu")
l_eqn = (
simplify(
-(beta * l + gamma)
* alpha
* (
27 * alpha**2
+ (l * alpha + 8 * beta) ** 2 / mu**2 / B
- l * gamma
- mu**2 * B * beta
).subs(mu**2, -1 / B * (l * beta + gamma) / alpha)
)
.expand()
.collect(l)
)
l_eqnfrom sympy import solve
l_val = solve(l_eqn.subs(q_vals), l)[0]
l_valFrom here, the corresponding values of are
# Check of the above:
I0 = (alpha * l**2 - 3 * beta * l - 3 * gamma) ** 3 / (
1728 * alpha**2 * (alpha * gamma * l - beta**2 * l - beta * gamma)
)
I0from sympy import radsimp
I_val = simplify(radsimp(I0.subs(q_vals).subs(l, l_val)))
I_val# Check of the above:
from sympy import Rational
mu_val = (1728 * I0 * alpha**2 - 8 * alpha * l**3 - 72 * l * (beta * l + gamma)) / (
l**2 * alpha + beta * l + gamma
)
mu_valmu_val = radsimp(mu_val.subs(q_vals).subs(l, l_val).expand())
mu_valNow, we do have to compute using the icosahedral equation below. (This is the only part of the computation that can’t be done using radicals.) In doing so, we recall that the inverse is given by a ratio of hypergeometric functions:
from sympy import hyper
def g_ratio_inf(sg_0, sg_1, sg_inf, x):
nu_0, nu_1, nu_inf = 3, 2, 5
a = Rational(1, 2) * (
1
- sg_0 * Rational(1, nu_0)
- sg_1 * Rational(1, nu_1)
+ sg_inf * Rational(1, nu_inf)
)
b = Rational(1, 2) * (
1
- sg_0 * Rational(1, nu_0)
- sg_1 * Rational(1, nu_1)
- sg_inf * Rational(1, nu_inf)
)
c = 1 - sg_0 * Rational(1, nu_0)
num = lambda z: z ** (-b) * hyper([b, 1 + b - c], [1 + b - a], 1 / z)
denom = lambda z: z ** (-a) * hyper([a, 1 + a - c], [1 + a - b], 1 / z)
return -((1728) ** Rational(1, 5)) * num(x) / denom(x)
z0_val = g_ratio_inf(1, 1, 1, I_val).evalf()
z0_valWith in tow, we can now compute the roots of the principal quintic:
from utils import hessian, z_simp
e, r = symbols("e r")
z_n, u, v = symbols("z_n u v")
t_0 = (
(u**2 + v**2)
* (u**2 - 2 * z_n * u * v - v**2)
* (u**2 - 2 * (-1 - z_n) * u * v - v**2)
)
t_r = simplify(z_simp((e ** (15 - 3 * r) * t_0.subs(v, v * e**r)).expand(), z_n))
W_r = simplify(hessian(t_r, u, v).expand() / (-400))from sympy import I, pi, exp
from icosahedron import f, T, H
def y_r(z_0, r_val):
e_val = (exp(2 * I * pi / 5)).evalf()
W_val = W_r.subs(u, z_0).subs(v, 1).subs(e, e_val).subs(r, r_val)
t_val = t_r.subs(u, z_0).subs(v, 1).subs(e, e_val).subs(r, r_val)
f_val = f.subs(u, z_0).subs(v, 1)
H_val = H.subs(u, z_0).subs(v, 1)
T_val = T.subs(u, z_0).subs(v, 1)
return (W_val * f_val / H_val * (l_val + mu_val * t_val * f_val**2 / T_val)).evalf()from sympy import conjugate
def ys(z_0, conj=False):
if conj:
return [conjugate(y_r(z_0, i)) for i in range(5)]
return [y_r(z_0, i) for i in range(5)]
def xs(z_0, conj=False):
yss = ys(z_0, conj=conj)
return [solve(yss[i] - x**2 - A_val * x - B_val, x)[0] for i in range(5)]
def icosahedral_orbit(z_seed, tol=1e-10):
"""Return all 60 preimages from a regular, finite seed."""
eps = exp(2 * I * pi / 5).evalf()
a = eps - eps**4
b = eps**3 - eps**2
points = [z_seed]
i = 0
while i < len(points):
z = points[i]
candidates = (
(eps * z).evalf(),
((a * z + b) / (b * z - a)).evalf(),
)
for candidate in candidates:
already_seen = any(
abs(candidate - old) <= tol * max(1, abs(candidate), abs(old))
for old in points
)
if not already_seen:
points.append(candidate)
if len(points) > 60:
raise RuntimeError("Orbit did not close; try a larger tolerance.")
i += 1
return pointsz_vals = icosahedral_orbit(z0_val)
len(z_vals)60for val in z_vals[:5]:
print(ys(val))[-8.49999999999997 + 20.7062792408486*I, -8.50000000000002 - 20.7062792408486*I, -11.0 + 5.5169898597468e-15*I, 24.0 - 2.28072769004051e-14*I, 4.0 + 5.97253095504374e-16*I]
[4.00000000000001 + 3.2816089255705e-15*I, -8.49999999999997 + 20.7062792408486*I, -8.50000000000002 - 20.7062792408486*I, -11.0 + 2.99115116356163e-15*I, 24.0 - 2.01198649754653e-14*I]
[-8.49999999999997 + 20.7062792408486*I, -11.0 + 1.62564325065576e-14*I, -8.5 - 20.7062792408486*I, 3.99999999999999 - 1.54896305200672e-14*I, 24.0 - 1.91632733986813e-16*I]
[24.0 - 2.95001653313753e-14*I, 4.00000000000001 - 2.21564845463706e-15*I, -8.49999999999998 + 20.7062792408487*I, -8.50000000000001 - 20.7062792408486*I, -11.0 + 2.10476167744611e-15*I]
[4.00000000000001 - 9.60273798412264e-15*I, -8.50000000000001 - 20.7062792408486*I, -8.49999999999997 + 20.7062792408486*I, 24.0 - 2.03325854417069e-14*I, -11.0 + 2.07939134660995e-15*I]
from sympy import Abs, N
def root_permutation(reference, roots, tol=1e-8):
"""Return labels p such that roots[i] ≈ reference[p[i] - 1]."""
if len(reference) != len(roots):
raise ValueError("Root lists must have equal lengths.")
permutation = []
for root in roots:
matches = [
j + 1 for j, ref in enumerate(reference) if Abs(N(root - ref, 25)) < tol
]
if len(matches) != 1:
raise ValueError("Root match is missing or ambiguous.")
permutation.append(matches[0])
if len(set(permutation)) != len(reference):
raise ValueError("Root matching is not bijective.")
return tuple(permutation)all_permutations = []
for val in z_vals:
all_permutations.append(root_permutation(ys(val), ys(z_vals[0])))
all_permutations.append(root_permutation(ys(val, conj=True), ys(z_vals[0])))all_permutations = []
for val in z_vals:
all_permutations.append(root_permutation(xs(val), xs(z_vals[0])))
all_permutations.append(root_permutation(xs(val, conj=True), xs(z_vals[0])))from itertools import permutations
exp_perm = []
for x in permutations(range(1, 6)):
exp_perm.append(x)
len(exp_perm)120exp_perm[:6][(1, 2, 3, 4, 5),
(1, 2, 3, 5, 4),
(1, 2, 4, 3, 5),
(1, 2, 4, 5, 3),
(1, 2, 5, 3, 4),
(1, 2, 5, 4, 3)]for perm in all_permutations:
if perm in exp_perm:
exp_perm.remove(perm)
else:
print("fail")
len(exp_perm)0y_vals = [y_r(z0_val, i) for i in range(5)]
y_vals[-8.49999999999997 + 20.7062792408486*I,
-8.50000000000002 - 20.7062792408486*I,
-11.0 + 5.5169898597468e-15*I,
24.0 - 2.28072769004051e-14*I,
4.0 + 5.97253095504374e-16*I]x_vals = [solve(y_vals[i] - x**2 - A_val * x - B_val, x) for i in range(5)]
x_vals[[-0.499999999999996 - 2.9580398915498*I, 6.5 + 2.9580398915498*I],
[-0.499999999999996 + 2.9580398915498*I, 6.5 - 2.9580398915498*I],
[2.0 - 2.7584949298734e-15*I, 4.0 + 2.7584949298734e-15*I],
[-3.0 + 1.90060640836709e-15*I, 9.0 - 1.90060640836709e-15*I],
[-1.0 - 7.46566369380468e-17*I, 7.0 + 7.46566369380468e-17*I]]from utils import reduce_multiply
reduce_multiply([(x - x_vals[i][1]) for i in range(5)]).expand()reduce_multiply([(x - x_vals[i][0]) for i in range(5)]).expand()reduce_multiply([(y - y_vals[i]) for i in range(2)]).expand()reduce_multiply([(x - x_vals[i][1]) for i in range(2)]).expand()((x - x_vals[0][0]) * (x - x_vals[-1][0])).expand()from utils import e_simp, hessian
u, v, e = symbols("u v e")
z_n = e**2 + e**3
z_m = e + e**4
def tI(u, v):
return (
(u**2 + v**2)
* (u**2 - 2 * z_n * u * v - v**2)
* (u**2 - 2 * z_m * u * v - v**2)
)
def t_r(r, z):
return e_simp(e ** (15 - 3 * r) * tI(z, v * e**r), e)
def W_r(r, z):
return (
simplify(e_simp(hessian(t_r(r, u), u, v).expand() / (-400), e))
.expand()
.subs(u, z)
.subs(v, 1)
)
t_r(2, u).subs(u, z0_val).evalf()W_r(2, u).subs(u, z0_val).evalf()from icosahedron import H, f, T
u, v = symbols("u v")
T.subs(u, z0_val).subs(v, 1).expand()# Check of the above:
from sympy import Add, Eq, Poly, discriminant, resultant, solve, symbols
x, p1, p2, p3, p4, p5 = symbols("x p1 p2 p3 p4 p5")
expr = x**5 + p1 * x**4 + p2 * x**3 + p3 * x**2 + p4 * x + p5
Eq(expr, 0)y, A, B, C = symbols("y A B C")
expr2 = Poly(resultant(expr, y - x**2 - A * x - C, x), y).terms()
expr2 = Add(*[b * y ** a[0] for a, b in expr2], evaluate=False)
expr2from sympy import Rational
expr3 = expr2.subs(p1, 0).subs(p3, 0).subs(C, 2 * p2 * Rational(1, 5))
expr3A2val = solve(expr3.coeff(y**3), A**2)[0]
A2valq4, q5 = symbols("q_4 q_5")
A2val.subs(p2, 10 * B).subs(p4, 45 * B**2 - 5 * B * q4).expand()expr4 = (
expr3
# .subs(A**5, A2val**2 * A)
# .subs(A**4, A2val**2)
# .subs(A**3, A2val * A)
# .subs(A**2, A2val)
.subs(A**2 * p2, (A2val * p2).expand())
# .subs(A**2*p2**2, (A2val*p2**2).expand())
# .subs(p4,Rational(45,100)*p2**2+q4)
# .subs(p5,p2**2*Rational(1,100)+q5)
# .subs(p2,10*B)
.expand()
.collect(y)
)
expr4alpha = Rational(1, 5) * expr4.coeff(y, 2)
alphaalpha.subs(A**2, A2val).subs(p2, 10 * B).subs(p4, 45 * B**2).subs(p5, B**2)beta = Rational(1, 5) * expr4.coeff(y, 1)
betabeta.subs(A**2, A2val).subs(A**4, A2val**2).subs(A**3, A2val * A).subs(p2, 10 * B).subs(
p4, 45 * B**2
).subs(p5, B**2)gamma = expr4.coeff(y, 0)
gammagamma.subs(A**5, A2val**2 * A).subs(A**2, A2val).subs(A**4, A2val**2).subs(
A**3, A2val * A
).subs(p2, 10 * B).subs(p4, 45 * B**2).subs(p5, B**2)gamma2 = (
gamma.subs(A**5, (-3 * B + q4) ** 2 * A)
.subs(A**2, -3 * B + q4)
.subs(p5, B**2 + q5)
.subs(p4, 45 * B**2 - 5 * B * q4)
.subs(p2, 10 * B)
.expand()
)
gamma2gamma_B = gamma2.subs(q4, 0).subs(q5, 0)
gamma_Bbeta2 = (
beta.subs(A**4, (-3 * B + q4) ** 2)
.subs(A**3, (-3 * B + q4) * A)
.subs(p4, 45 * B**2 - 5 * B * q4)
.subs(p5, B**2 + q5)
.subs(p2, 10 * B)
.expand()
)
beta2beta_B = beta2.subs(q4, 0).subs(q5, 0)
beta_Balpha2 = (
alpha.subs(A**2, -3 * B + q4)
.subs(p4, 45 * B**2 - 5 * B * q4)
.subs(p2, 10 * B)
.subs(p5, B**2 + q5)
.expand()
)
alpha2alpha_B = alpha2.subs(q4, 0).subs(q5, 0)
alpha_Bl = symbols("lambda")
alpha, beta, gamma = symbols("alpha beta gamma")
l_eqn = (
(
(alpha**4 - beta**3 + alpha * beta * gamma) * l**2
+ (alpha * gamma**2 - 2 * beta**2 * gamma - 11 * alpha**3 * beta) * l
+ 64 * alpha**2 * beta**2
- 27 * alpha**3 * gamma
- beta * gamma**2
)
.expand()
# .subs(A**2, A2val)
# .subs(A**4, A2val**2)
# .subs(A**3, A2val * A)
# .expand()
.collect(l)
)
l_eqnl_eqn_B = (
l_eqn.subs(alpha, alpha_B)
.subs(beta, beta_B)
.subs(gamma, gamma_B)
.expand()
.collect(A)
)
l_eqn_Bdef A_simp(expr, A, B):
poly = Poly(expr, A)
coeffs = poly.all_coeffs()
while len(coeffs) > 2:
top_factor = coeffs[0]
coeffs[2] += (-3 * B) * top_factor
coeffs.pop(0)
return Poly.from_list(coeffs, A).as_expr()
l_eqn_B2 = A_simp(l_eqn_B, A, B).expand().collect(l)
l_eqn_B2discriminant(l_eqn_B, l).factor()A_simp(discriminant(l_eqn_B2, l), A, B).factor()l_vals = solve(l_eqn_B2, l)
l_vals[0].factor()l_vals[1]