By now, it should be clear that — given an arbitrary quintic equation — it’s possible to massage this quintic into a form that can be used to easily determine the roots: first one passes from the general quintic to a corresponding “principal quintic” with roots given by
where , are determined in terms of the coefficients of the principal quintic. Once these roots are computed, the corresponding roots of the general quintic are found by solving one last quadratic equation:
for some , which determine the principal quintic, given the coefficients of the original quintic.
The Icosahedral Inverse and the Schwarzian¶
We wish to formally invert the equation
such that a value can be computed from a known icosahedral parameter . In other words, we seek a function such that for any away from the singular points .
It has been shown previously that the function maps vertices, edge midpoints, and face centres to the singular points , respectively. also satisfies the symmetries of the icosahedron. As such, we expect that — once we invert it on a small triangle inscribed by the points
near the top of the icosahedron (where ) — we’ll find that we can uniquely define a particular for every (up to some conditions, which we’ll specify). (Recall the parameters , , from earlier sections, which made easy the computation of the icosahedral-invariant polynomials; they are defined to be
in terms of the angle subtended by the two-fold symmetry axis and the vertical.
We only expect the function to have an inverse on this region and its complex conjugate, since the icosahedral symmetries will map out every other found to reproduce a particular (and so the inverse would be multi-valued outside of this wedge). These singular points allow us to uniquely determine the Schwarzian of , as we’ll see next.
Consider the series expansion of about the three singular points :
Note that, of these three expansions, only the one about embodies the corresponding -fold symmetry about that point. The reason for this is that the origin coincides with the five-fold axis of symmetry, since the antipodes along this axis are .
Given this observation, we can perform a Möbius transformation which maps , to in each case:
Before expanding about , let’s first consider the icosahedral parameter by substituting (found by inverting the above and substituting , ):
from icosahedron import f
from utils import z_simp, j_simp
from sympy import symbols
u, v = symbols("u v")
z_n, j, w = symbols("z_n j w")
v_20 = z_n - j
v_1 = z_n + j
f2w = (
z_simp(j_simp(f.subs(u, w * v_20 - v_1).subs(v, w - 1).expand(), j, z_n), z_n)
.expand()
.collect(w)
)
f2wfrom icosahedron import H
H2w = (
z_simp(j_simp(H.subs(u, w * v_20 - v_1).subs(v, w - 1).expand(), j, z_n), z_n)
.expand()
.collect(w)
)
H2wSince both and are functions of , the two-fold symmetry is now explicit. Performing the series expansion about , we find
Rewriting in terms of , we have the resummed expansion
from sympy import series, Poly
p = symbols("p")
I2p = -((H2w.subs(w**2, p)) ** 3) / 12**3 / (f2w.subs(w**2, p)) ** 5
I2p_series = series(I2p, p, 0, n=3)
I2p_seriesfrom sympy import Poly, latex
from utils import rationalize_denom
expr = Poly(I2p_series.removeO(), p)
new_coeffs = []
for c in expr.all_coeffs():
new_coeffs.append(z_simp(j_simp(rationalize_denom(c), j, z_n), z_n))
I2p_series = Poly.from_list(new_coeffs, p).as_expr()
I2p_seriesSimilarly, if we instead transform such that go to , i.e.
then we find that is instead a rational polynomial of :
Rewriting in terms of , we have the resummed expansion at :
k = symbols("k")
w_1 = (1 - (1 + k) * z_n) / 2rationalize_denom(-1 / w_1)e = symbols("e")
w_3 = e**3 * w_1
w_27 = e**3 * ((1 - (1 - k) * z_n) / 2)from utils import k_simp, e_simp
Hw3 = (
e_simp(
z_simp(k_simp(H.subs(u, w * w_27 - w_3).subs(v, w - 1).expand(), k, z_n), z_n),
e,
)
.expand()
.collect(w)
)
Hw3from utils import k_simp, e_simp
fw3 = (
e_simp(
z_simp(k_simp(f.subs(u, w * w_27 - w_3).subs(v, w - 1).expand(), k, z_n), z_n),
e,
)
.expand()
.collect(w)
)
fw3.factor()from sympy import series, Poly
q = symbols("q")
I3q = -(Hw3**3).expand().subs(w**3, q) / 12**3 / (fw3.subs(w**3, q).subs(e, 1)) ** 5
I3q_series = series(I3q, q, 0, n=3)
expr = Poly(I3q_series.removeO(), q)
new_coeffs = []
for c in expr.all_coeffs():
new_coeffs.append(z_simp(k_simp(rationalize_denom(c), k, z_n), z_n))
I3q_series = Poly.from_list(new_coeffs, q).as_expr()
I3q_seriesrationalize_denom(w_3 - w_27)Lastly, for completeness, we can apply the same reasoning to expand at by transforming to (swapping antipodes) and expanding as before as a function of :
from icosahedron import I as I_invar
from sympy import symbols, series
r, u, v, w = symbols("r u v w")
I5r = I_invar.subs(u, 1).subs(v, -w).expand().subs(w**5, r)
I5r.factor()r_ser = series(I5r, r, 0, n=5).removeO()
r_serMoreover, the resummed expressions can be easily inverted to give the corresponding expansions of the inverse about each singular point: since has the general form
for with (we denote the antipode to as ) then the corresponding inverse is defined implicitly to be
(from which ).
The above gives us insight into the structure of singular points of , as we’ll now see. Consider the Schwarzian, given by
Any Möbius transformation has zero Schwarzian (by definition), so applying the Schwarzian to the above power series gives
Therefore, the leading and subleading terms in each expansion give all the singular behaviour in the Schwarzian.
from sympy import diff, Rational, Integer, simplify, oo, solve
def schwarz(f, x):
ratio = diff(f, x, x) / diff(f, x)
return simplify(diff(ratio, x) - Rational(1, 2) * ratio**2)
x, x0, A, B = symbols("x x_0 A B")
n = symbols("n", positive=True)
k = symbols("k", positive=True)
# A = symbols("A", positive=True)
f = ((x - x0) / A) ** (1 / n) * (1 - B / n / A * ((x - x0) / A))
f_ser = series(schwarz(f, x), x, x0, n=0)
f_sersimplify(f_ser.coeff(x - x0, -2)).factor()simplify(f_ser.coeff(x - x0, -1)).factor()Near , we that — although the expressions for and are quite complex (involving square roots of square roots via ) — we find that the ratio
gives a rational number.
I3q_A = I3q_series.coeff(q, 1)
I3q_AI3q_B = I3q_series.coeff(q, 2)
I3q_Brationalize_denom(I3q_B / I3q_A**2)I2p_A = I2p_series.coeff(p, 1)
I2p_AI2p_B = I2p_series.coeff(p, 2)
I2p_Bz_simp(j_simp(rationalize_denom(I2p_B / I2p_A**2), j, z_n), z_n)We’ll see that the reason for this later on, but, to preview: the vertices , , and form a Schwarz triangle on the Riemann sphere with internal angles , where
The properties of analytic functions on a Schwarz triangle are well-known, but it’s sufficient to know that the singularities near , — together with the asymptotic expansion near — are sufficient to determine the Schwarzian of . In particular, we’ll find that
where , , and .
As , we find instead that the inverted series has the form
where and . We can show this by inverting the series for , i.e.
to determine (for which ), and then taking the fifth root on the principal branch (since ) to determine :
n = 6
c = symbols(f"c0:{n}", real=True)
d = symbols(f"d0:{n}", real=True)
r_inv = (1 / x * sum([c[i] / x**i for i in range(n)])).expand()
I_tilde = (1 / r * sum([d[i] * r**i for i in range(n)])).expand()
I_tilde2 = I_tilde.subs(r, r_inv)
I_tilde2 = series(I_tilde2, x, oo, n=n).removeO().expand().collect(x)
I_tilde2.collect(x)coeffs = [1] + [0] * (n - 1) # enforce that r_inv(I_tilde(r)) = 1 asymptotically
d_vals = {d[i]: (r_ser * 1728 * r).expand().coeff(r, i) for i in range(n)}
eqs = [
I_tilde2.coeff(x, i).subs(d_vals) - val for i, val in zip(range(1, -n, -1), coeffs)
]
c_vals = solve(eqs, c, dict=True)
r_inv = r_inv.subs(c_vals[0]).expand().collect(x)
r_invr_inv_part = series(
((r_inv * x).expand().subs(x, 1728 * x).subs(d_vals)) ** Rational(-1, 5),
x,
oo,
n=8,
).removeO()
r_inv_asympt = -r_inv_part * (x * 12**3) ** (Rational(1, 5))
r_inv_asymptComputing the Schwarzian of the asymptotic expansion above, we find
series(schwarz(r_inv_asympt, x), x, oo, n=8).removeO()Combining these with the expansions about , namely
we find that, indeed, these asymptotic expressions can be found by expanding the full expression
in the appropriate limits.
nu1, nu2, nu3 = symbols("nu_1 nu_0 \\nu_{\\infty}")
nu_vals = {nu1: 2, nu2: 3, nu3: 5}
beta = (1 - 1 / nu1**2) / 2 + (1 - 1 / nu2**2) / 2 - (1 - 1 / nu3**2) / 2
beta.subs(nu_vals)Ss_full = (
(1 - 1 / nu1**2) / 2 / (x - 1) ** 2
+ (1 - 1 / nu2**2) / 2 / x**2
+ beta / x / (1 - x)
)
Ss_fullseries(Ss_full, x, 0, n=0).subs(nu_vals)series(Ss_full, x, 1, n=0).subs(nu_vals)series(Ss_full, x, oo, n=8).removeO().subs(nu_vals)As one last check of the expression above for , one can take the demonstrated leading behaviour of at and show that, in order for
as well, the singular terms must have exactly this form. To see this, take the expression
and expand:
(We could instead consider to be a regular function of , but Schwarz reflection across the real line would then impose that it be constant.) At each order, the conditions are at , at , and at .
from sympy import Function
x, nu, gamma = symbols("x nu gamma")
alpha = (1 - 1 / nu**2) / 2
nu_0, nu_1 = symbols("nu_0 nu_1")
alpha_0 = alpha.subs(nu, nu_0)
alpha_1 = alpha.subs(nu, nu_1)
beta_0, beta_1 = symbols("beta_0 beta_1")
schwarzian_of_s = (
alpha_0 / x**2 + alpha_1 / (x - 1) ** 2 + beta_0 / x + beta_1 / (x - 1) + gamma
)
schwarzian_of_sschwarz_inf = series(schwarzian_of_s, x, oo, n=4)
schwarz_infnu_inf = symbols("\\nu_{\\infty}")
solve(schwarz_inf.coeff(x, -2) - (1 - 1 / nu_inf**2) / 2, beta_1)[0]Solving the Schwarzian¶
It can be shown that the general solution to the the Schwarzian equation is given by
where , are two independent solutions (i.e. with Wronskian ) to the second-order equation
Since the singular points of are at 0, 1, and , we expect the hypergeometric function
to play a prominent role. It’s well understood to be a solution of the differential equation
which can be rewritten as
by substituting . (The prefactor has been engineered to cancel the first-derivative term, but is nevertheless unimportant once the ratio is computed.)
from sympy import exp, dsolve, diff
a, b, c, z = symbols("a b c z")
f = Function("f")
g = Function("g")
h = Function("h")
phi = Function("phi")
hypergeom = (
diff(h(z), z, z)
+ (c - (a + b + 1) * z) / z / (1 - z) * diff(h(z), z)
- a * b * h(z) / z / (1 - z)
)
hypergeom_sub = (hypergeom.subs(h(z), exp(phi(z)) * g(z)).doit() / exp(phi(z))).expand()
diffterm = hypergeom_sub.coeff(diff(g(z), z))
diffterm = simplify(diffterm)
difftermresult = dsolve(diffterm, phi(z))
simplify(result)simplify(hypergeom_sub.subs(phi(z), result.rhs).doit().expand()).collect(g(z))a, b, c = symbols("a b c")
f_num = simplify(Ss_full * 2 * x**2 * (1 - x) ** 2)
f_num.subs(1 / nu_0**2, (1 - c) ** 2).subs(1 / nu_1**2, (c - a - b) ** 2).subs(
1 / nu_inf**2, (a - b) ** 2
).expand().collect(x)
Denoting the sign choices as , , (where ), we can invert and find
res = solve([1 / nu_0 - 1 + c, 1 / nu_1 - c + a + b, 1 / nu_inf - a + b], [a, b, c])
res[a]res[b]res[c]Since we’d like to fix such that , we’ll choose the expansion point for the hypergeometric solutions to be . As such, the linearly independent solutions in this case are
To fix the overall scale of , as well as the sign , we examine the asymptotic expansion of the ratio as and determine that
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 num(x) / denom(x)
series(g_ratio_inf(1, 1, 1, x), x, oo, n=3)series(g_ratio_inf(1, 1, -1, x), x, oo, n=3)Since we expect the expansion at to be local, it’s expected to not depend on the sign choices , . However, must be chosen to be positive such that series expansion agrees with the inverted series found previously.
Similarly, if take the expansion point to be , we find that the ratio
(with ) gives the appropriate expansion near .
def g_ratio_0(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 ** (1 - c) * hyper([1 + a - c, 1 + b - c], [2 - c], z)
denom = lambda z: hyper([a, b], [c], z)
return num(x) / denom(x)
series(g_ratio_0(1, 1, 1, x), x, 0, n=3)series(g_ratio_0(-1, 1, 1, x), x, 0, n=3)def g_ratio_1(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: (
(1 - z) ** (c - a - b) * hyper([c - a, c - b], [1 + c - a - b], 1 - z)
)
denom = lambda z: hyper([a, b], [1 + a + b - c], 1 - z)
return num(x) / denom(x)
series(g_ratio_1(1, 1, 1, x), x, 1, n=3, dir="-")series(g_ratio_1(1, -1, 1, x), x, 1, n=3, dir="-")