8.5 Series solutions in software
Software 8.5.1 (The Airy function from its series). The recursion of Example 8.1.2, with initial values \(\operatorname {Ai}(0)\) and \(\operatorname {Ai}'(0)\) taken from SciPy, summed to \(60\) terms and compared with SciPy’s airy:
>>> import numpy as np >>> from math import factorial >>> from scipy import special >>> def airy_series(a0, a1, t, K=60): # y'' = t y, power series at 0 ... a = [a0, a1, 0.0] ... for k in range(1, K): ... a.append(a[k - 1] / ((k + 2) * (k + 1))) ... return sum(c * t**j for j, c in enumerate(a)) ... >>> c1, c2 = special.airy(0)[0], -special.airy(0)[1] # Ai(0), -Ai'(0) >>> for t in (-4.0, 0.5, 2.0): ... print(t, airy_series(c1, -c2, t), special.airy(t)[0]) ... -4.0 -0.07026553294928879 -0.07026553294928964 0.5 0.23169360648083345 0.23169360648083343 2.0 0.034924130423274184 0.03492413042327436
Reading the output. The series agrees with the library value to about \(14\) significant digits at all three points. At \(t = -4\), where the solution oscillates, the sum is made of terms as large as \(4\) in modulus that cancel to \(-0.07\), and the cancellation grows rapidly with \(|t|\). For large \(|t|\) the series, although convergent, becomes useless numerically because of cancellation, and libraries switch to asymptotic expansions there.
MATLAB/Octave: airy(0, [-4 0.5 2]).
Software 8.5.2 (Bessel functions). Partial sums of the series for \(J_0(5)\), the first zeros of \(J_0\), and the closed form of \(J_{1/2}\):
>>> J0 = lambda t, K: sum((-1)**k * (t / 2)**(2 * k) / factorial(k)**2 ... for k in range(K)) >>> [round(J0(5.0, K), 6) for K in (5, 10, 15, 20)] [0.383036, -0.177603, -0.177597, -0.177597] >>> round(special.jv(0, 5.0), 6) -0.177597 >>> special.jn_zeros(0, 5) array([ 2.40482556, 5.52007811, 8.65372791, 11.79153444, 14.93091771]) >>> np.diff(special.jn_zeros(0, 5)), np.pi (array([3.11525255, 3.1336498 , 3.13780653, 3.13938327]), 3.141592653589793) >>> t = 3.0; special.jv(0.5, t), np.sqrt(2 / (np.pi * t)) * np.sin(t) (0.06500818287737586, 0.06500818287737577)
Reading the output. Five terms of the series give \(0.383\), nowhere near \(J_0(5) = -0.177597\); ten give it to five decimals, and fifteen to all six shown. The zeros of \(J_0\) are \(2.405, 5.520, 8.654, \dots \), and their spacings \(3.115, 3.134, 3.138, 3.139\) approach \(\pi \), as the asymptotic form predicts. \(J_{1/2}(3)\) equals \(\sqrt {2/(3\pi )}\sin 3\), confirming Theorem 8.3.1(iv).
MATLAB/Octave: besselj(0, 5); the zeros with fzero(@(t) besselj(0, t), 2.4).
Software 8.5.3 (Legendre polynomials). Rodrigues’ formula in SymPy, checked against the built-in legendre, and the matrix of inner products \(\int _{-1}^1P_mP_n\):
>>> import sympy as sp
>>> x = sp.symbols('x')
>>> rod = lambda n: sp.expand(sp.diff((x**2 - 1)**n, x, n)
... / (2**n * factorial(n))) # Rodrigues
>>> [rod(n) for n in range(4)]
[1, x, 3*x**2/2 - 1/2, 5*x**3/2 - 3*x/2]
>>> rod(3) == sp.expand(sp.legendre(3, x))
True
>>> [[sp.integrate(rod(m) * rod(n), (x, -1, 1)) for n in range(4)]
... for m in range(4)]
[[2, 0, 0, 0], [0, 2/3, 0, 0], [0, 0, 2/5, 0], [0, 0, 0, 2/7]]
Reading the output. Rodrigues’ formula gives \(1\), \(x\), \(\frac 32x^2 - \frac 12\) and \(\frac 52x^3 - \frac 32x\), the first four Legendre polynomials, and agrees with SymPy’s own. The matrix of integrals is diagonal, with entries \(2, \frac 23, \frac 25, \frac 27\), which are \(\frac {2}{2n + 1}\): Theorem 8.4.1(ii) exactly.
MATLAB/Octave: legendreP(3, x) with the Symbolic Math Toolbox.
Questions on this section
Stuck on something here? Ask below and it stays attached to this topic.