Chapter 4.1-4.3
Chapter 4.1-4.3
The solution of this equation is x(t) = x0 e−λt , where x0 = x(0) is said to be the initial
value. While it is straightforward to solve a linear differential equation, in which all
terms are linear in x or ẋ, non-linear differential equations are more challenging.
Consider the Bernoulli equation1 :
where α(t) and β(t) are given functions and ρ is a real number. It encompasses
important differential equations as special cases, for example, Eq. (4.1) follows for
α(t) = λ, β(t) = 0, ρ = 0 and the logistic equation describing population dynamics
for α(t) = a, β(t) = b with constants a > 0, b < 0, and ρ = 2 (see Exercise 4.1).
From the viewpoint of numerics, Bernoulli-type equations are interesting because
an analytic solution is known and can be compared to numerical approximations.
In the following, we will attempt to numerically solve an example for a Bernoulli
equation from astrophysics, namely the equation for the radial expansion of a so-
called Strömgren sphere. When a hot, massive star is borne, it floods its surroundings
with strongly ionizing UV radiation. As a result, a spherical bubble of ionized hydro-
gen (H II) forms around the star. It turns out that ionization progresses as ionization
front, i.e. a thin spherical shell propagating outwards (see [4], Sect. 12.3). Inside the
shell, virtually all hydrogen is ionized. The radial propagation of the shell is described
by a differential equation for the time-dependent radius r (t) [9] (convince yourself
that this equation is a Bernoulli differential equation):
1 4π 3 2
ṙ = S∗ − r n0α (4.3)
4πr 2 n 0 3
Here, S∗ is the total number of ionizing photons (i.e. photons of energy greater
than 13.6 eV; see Sect. 3.2.1) per unit time, n 0 is the number density of neutral
hydrogen atoms (H I), and α ≈ 3.1 × 10−13 cm3 s−1 is the recombination coefficient.
Recombination of ionized hydrogen and electrons competes with ionization of neutral
hydrogen.2
Equation (4.3) can be rewritten in the form
rs3 − r 3
ṙ = n 0 α (4.4)
3r 2
where 1/3
3S∗
rs = (4.5)
4πn 20 α
1 Named after the Swiss mathematician Jakob Bernoulli, one of the pioneers of calculus in the late
17th century.
2 The recombination rate is proportional to the product of the number densities of hydrogen ions and
electrons, which is n 20 in the case of a fully ionized medium. The total number of recombinations
per unit time is obtained by multiplying with the volume of the sphere.
4.1 Numerical Integration of Initial Value Problems 107
is the Strömgren radius. The speed ṙ approaches zero for r → rs , i.e. the propagation
of the ionization front slows down and stalls at the Strömgren radius.3 How large is
a Strömgren sphere? Let us do the calculation for an O6 star (see also [4], example
12.4):
1 import numpy
2 import astropy.units as unit
3
4 n0 = 5000 * 1/unit.cm**3 # number density of HI
5 S = 1.6e49 * 1/unit.s # ionizing photons emitted per second
6 alpha = 3.1e-13 * unit.cm**3/unit.s # recombination coefficient
7
8 rs = (3*S/(4*np.pi * n0**2 * alpha))**(1/3)
9 print("Strmoegren radius = {:.2f}".format(rs.to(unit.pc)))
The photons emitted by the star per second can be estimated from the luminosity
and the peak of the Planck spectrum for an effective temperature Teff ≈ 4.5 × 104 K.
Astropy’s units module allows us to express the result in parsec without bothering
about conversion factors (see also Sect. 3.1.1):
To integrate Eq. (4.4) in time, we can apply a linear approximation over a small
time interval [t, t + t] (also called time step):
In other words, it is assumed that the derivative ṙ changes only little over t and can
be approximated by the value at time t. For given r (t), we can then substitute the
right-hand side of the differential equation for ṙ (t):
rs3 − r (t)3
r (t + t) r (t) + n 0 α t
3r (t)2
Starting with an initial value r (0) = r0 , this rule can be applied iteratively for tn =
nt. This is the basic idea of the Euler method.4 For a general first-order differential
equation
ẋ = f (t, x) , (4.7)
3 Infact, the sphere of ionized hydrogen begins to expand once ṙ drops below the speed of sound.
The expansion stops when it reaches pressure equilibrium with the surrounding neutral medium.
4 The method was introduced by Leonhard Euler in his influential textbook on calculus from 1768,
long before it was possible to routinely carry out numerical approximations with the help of com-
puters.
108 4 Solving Differential Equations
Fig. 4.1 Numerical solution of the initial value problem (4.4) for a Strömgren sphere with r0 =
1016 cm using the Euler method (dashed line). The analytic solution for r0 = 0 is shown as solid
line
is plotted in lines 33–35 (see solid line in Fig. 4.1). It agrees quite well with our
numerical solution, except for the discrepancy at early time. Actually, this is mainly
caused by different zero points of the time coordinates. For numerical integration, we
assume the initial value r (0) = r0 , while formula (4.9) implies r (0) = 0. This can
be fixed by shifting the time coordinate of the analytic solution such that r (0) = r0 .
Solving Eq. (4.9) with r = r0 for time yields
1
t0 = log 1 − (r0 /rs )3 . (4.10)
n0α
110 4 Solving Differential Equations
For a radius of about 0.3 pc, this is very fast and indicates that the propagation speed
of the ionization front must be quite high (see Exercise 4.2). It turns out that the final
time of our numerical solution (1000 time steps) is only a tiny fraction of ts :
43 t[-1]*unit.s/ts
The output is
0.000155
As a result, we would need roughly 104 × 1000 ∼ 107 time steps to reach ts . This
is a very large number of steps. To compute the time evolution for a longer interval
of time, we can increase the time step t, which in turn reduces the number of time
steps. However, keep in mind that Eq. (4.8) is an approximation. We need to weigh
the reduction of computational cost (fewer steps) against the loss of accuracy (larger
time step).
For a sensible choice, it is important to be aware of the physical scales char-
acterizing the system. The differential equation for the time-dependent radius of a
Strömgren sphere can be expressed in terms of the dimensionless variables r̃ = r/rs
and t˜ = t/ts :
dr̃ 1 − r̃ 3
= . (4.11)
dt˜ 3r̃ 2
In this form, it is much easier to choose initial values and an appropriate integration
interval. The initial radius should be small compared to the Strömgren radius, for
example, r̃0 = 0.01. To follow the evolution of the sphere over the time scale ts , we
need to integrate at least over the interval [0, 1] with respect to t˜ (in the example above,
the interval was [0, 0.000155]). Obviously, the times step must be small compared
to ts , i.e. t/ts ≡ t˜
1. The following code computes and plots the numerical
solution for different time steps, starting with t˜ = 10−3 .
4.1 Numerical Integration of Initial Value Problems 111
Fig. 4.2 Numerical solutions of the differential equation in dimensionless formulation (4.11) for
different time steps (t˜ = 10−3 , 10−4 , and 10−5 ; the corresponding physical time steps are indi-
cated in the legend). The Strömgren radius and formation time are rs = 0.26 pc and ts = 20.4 yr,
respectively
This examples shows how to plot multiple graphs from within a loop. The time step
is iterated in a while loop. The nested for loop starting at line 66 applies the Euler
scheme to Eq. (4.11). Here, the arrays t and r contain values of the dimensionless
variables t˜ and r̃ . It is important to create these arrays for each iteration of the outer
loop (see lines 60–61) because the number of time steps and, consequently, the array
size increases. After plotting r versus t, the time step is reduced by a factor of 10
(line 76) and the number of steps increases by a factor of 10 to reach the same end
point (line 77). The while loop terminates if t˜ < 10−5 . The physical time step in
hours is printed in the legend (the label is produced in lines 72–73). Prior to the loop,
we print the analytic solution with the shifted time coordinate defined by Eq. (4.10)
(see lines 48–53). The results are shown in Fig. 4.2.
The largest time step, t˜ = 10−3 clearly does not work. The initial speed goes
through the roof, and the radius becomes by far too large. The solution for t˜ = 10−4
improves somewhat, but the problem is still there. Only the smallest time step, t˜ =
10−5 , results in an acceptable approximation with small relative deviation from the
analytic solution. However, 200 000 steps are required in this case. It appears to be
quite challenging to solve this problem numerically. Can we do better than that?
The Euler method is the simplest method of solving a first order differential
equation. There are more sophisticated and more accurate methods. An example
is the Runge-Kutta method, which is a higher-order method.5 This means that the
discretization error for a finite time step t is of the order t n , where n > 2. In the
5 A different approach are variable step-size methods. The idea is to adapt the time step based on some
error estimate. An example is the Bulirsch-Stoer method. Such a method would be beneficial for
treating the initial expansion of a Strömgren sphere. However, with the speed of modern computers,
4.1 Numerical Integration of Initial Value Problems 113
case of the classic Runge-Kutta method, the error is of order t 5 . Thus, it is said to be
fourth-order accurate and in short called RK4.6 In contrast, the Euler method is only
first-order accurate. This can be seen by comparing the linear approximation (4.6)
to the Taylor series expansion
1
r (t + t) = r (t) + ṙ (t)t + r̈ (t)t 2 + . . .
2
The Euler method follows by truncating all terms of order t 2 and higher. For
this reason, the discretization error is also called truncation error. But how can we
extend a numerical scheme for an initial value problem to higher order? The first
order differential equation (4.7) determines the first derivative ẋ for given t and x,
but not higher derivatives (do not confuse the order with respect to the truncation
error and the order of the differential equation). Nevertheless, higher-order accuracy
can be achieved by combining different estimates of the slope ẋ at the subinterval
endpoints t and t + t and the midpoint t + t/2.
The RK4 scheme is defined by
k1 = f (t, x) t ,
k2 = f (t + t/2, x + k1 /2) t ,
k3 = f (t + t/2, x + k2 /2) t ,
k4 = f (t + t, x + k3 ) t ,
and
1
x(t + t) = x(t) + [k1 + 2(k2 + k3 ) + k4 ] . (4.12)
6
Here, k1 is the increment of x(t) corresponding to the Euler method, k2 /2 is the
interpolated increment for half of the time step, k3 /t is the corrected midpoint
slope based on x + k2 /2, and k4 /t the resulting slope at the subinterval endpoint
t + t. Equation (4.12) combines these estimates in a weighted average.
Let us put this into work. Since we need to compute multiple function values
for each time step, it is convenient to define the RK4 scheme as a Python function,
similar to the numerical integration schemes in Sect. 3.2.2:
1 def rk4_step(f, t, x, dt):
2
3 k1 = dt * f(t, x)
4 k2 = dt * f(t + 0.5*dt, x + 0.5*k1)
5 k3 = dt * f(t + 0.5*dt, x + 0.5*k2)
preference is given to higher-order methods. If you nevertheless want to learn more, you can find a
Fortran version of the Bulirsch-Stoer method in [10].
6 The (local) truncation error is an error per time step. The accumulated error or global truncation
error from the start to the endpoint of integration is usually one order lower, i.e. of order t 4 in
the case of the RK4 method.
114 4 Solving Differential Equations
Fig. 4.3 Same plot as in Fig. 4.2 with the Runge-Kutta (RK4) method instead of the Euler method
The Runge-Kutta integrator is called iteratively in lines 24–25. The values passed
to rk4_step() are t[n] and x[n]. Moreover, we have to specify the derivate
on the right-hand side of the differential equation. For the Strömgren sphere, the
mathematical definition reads
1 − r̃ 3
f (t˜, r̃ ) = .
3r̃ 2
This can be easily translated into a Python function. Since we need this function only
as input of rk4_step(), we use an anonymous function rather than a named func-
tion (see Sect. 3.2.2). The only pitfall is that our lambda must accept two arguments
(listed after the keyword lambda), even though only one of them occurs in the
expression following the colon (in other words, f (t˜, r̃ ) is actually independent of t˜).
Of course, the same would apply if we had used a named function. Both arguments
are necessary because the function is called with these two arguments in the body of
rk4_step() (see lines 3–6). You can try to remove the argument t in line 24 and
take a look at the ensuing error messages.
Compared to the first-order Euler method, the quality of the numerical solutions
computed with RK4 improves noticably. As shown in Fig. 4.3, the analytic solution
is closely reproduced for dt˜ = 10−4 or smaller. The deviation between the numerical
and analytic values at the end of integration is printed for each time step in lines
32–33:
The deviation decreases substantially for smaller time steps. While methods of higher
order are more accurate than methods of lower order, they require a larger number
of function evaluations. This may be costly depending on the complexity of the
differential equation. With four function evaluations, RK4 is often considered a
reasonable compromise between computational cost and accuracy. Other variants
of the Runge-Kutta method may require a larger or smaller number of evaluations,
resulting in higher or lower order. In Appendix B.2, optimization techniques are
explained that will enable you to speed up the execution of the Runge-Kutta step.
For the example discussed in this section, we know the analytic solution. This
allows us to test numerical methods. However, if we apply these methods to another
differential equation, can we be sure that the quality of the solution will be comparable
for a given time step? The answer is clearly no. One can get a handle on error
estimation from mathematical theory, but often it is not obvious how to choose an
appropriate time step for a particular initial value problem. For such applications, it
is important to check the convergence of the solution for successively smaller time
steps. If changes are small if the time step decreases, such as in the example above,
chances are good that the numerical solution is valid (although there is no guarantee
in the strict mathematical sense).
In mechanics, we typically deal with second order differential equations of the form
where x(t) is the unknown position function, ẋ(t) the velocity, and ẍ(t) the acceler-
ation of an object of mass m (which is hidden as parameter in the function f ). The
differential equation allows us to determine x(t) for a given initial position x0 = x(t0 )
and velocity v0 = ẋ(t0 ) at any subsequent time t > t0 . For this reason, it is called
equation of motion.
A very simple example is free fall:
ẍ = g , (4.14)
where x is the coordinate of the falling mass in vertical direction. Close to the surface
of Earth, g ≈ 9.81 m s−2 and f (t, x, ẋ) = g is constant. Of course, you know the
solution of the initial value problem for this equation:
1
x(t) = x0 + v0 (t − t0 ) + g(t − t0 )2 . (4.15)
2
For larger distances from ground, however, the approximation f (t, x, ẋ) = g is not
applicable and we need to use the 1/r gravitational potential of Earth. In this case,
4.1 Numerical Integration of Initial Value Problems 117
m ẍ + kx = 0 , (4.16)
which is equivalent to
k
ẍ = f (x) where f (x) = − x. (4.17)
m
The coefficient k is called spring constant for the archetypal oscillation of a mass
attached to a spring. You can easily check by substitution that the position function
m ẍ + d ẋ + kx = 0 , (4.19)
d k
f (x, ẋ) = − ẋ − x , (4.20)
m m
This is readily translated into a Python function:
1 def xddot(t, x, xdot, m, d, k):
2 """
3 acceleration function of damped harmonic oscillator
4
5 args: t - time
6 x - position
7 xdot - velocity
8 m - mass
9 d - damping constant
10 k - spring constant
11
12 returns: positions (unit amplitude)
13 """
14 return -(d*xdot + k*x)/m
In the following, we use a unit system that comes under the name of arbitrary units.
This is to say that we are not interested in the specific dimensions of the system,
but only in relative numbers. If you have the impression that this is just what we did
when normalizing radius and time for the Strömgren sphere in the previous section,
then you are absolutely right. Working with arbitrary units is just the lazy way of
introducing dimensionless quantities. From the programmer’s point of view, this
means that all variables are just numbers.
The Euler method introduced in Sect. 4.1.1 can be extended to second order initial
value problems by approximating the velocity difference over a finite time step t
as
v = ẍt f (t, x, ẋ)t
starting from initial data x0 and ẋ0 . This is a specific choice. As we will see, there
are others.
To implement this method, we define a Python function similar to rk4_step()
for first-order differential equations:
15 euler_forward(f, t, x, xdot, h, *args):
16 """
17 Euler forward step for function x(t)
18 given by second order differential equation
19
20 args: f - function determining second derivative
21 t - value of independent variable t
22 x - value of x(t)
23 xdot - value of first derivative dx/dt
4.1 Numerical Integration of Initial Value Problems 119
24 h - time step
25 args - parameters
26
27 returns: iterated values for t + h
28 """
29 return ( x + h*xdot, xdot + h*f(t, x, xdot, *args) )
Compared to rk4_step(), there are two differences. First, the updated position
xn+1 and velocity ẋn+1 defined by Eqs. (4.21) and (4.22), respectively, are returned
as tuple. Second, euler_forward() admits so-called variadic arguments. The
following example shows how to use it:
30 import numpy as np
31
32 # parameters
33 m = 1.
34 d = 0.05
35 k = 0.5
36 x0 = 10
37
38 n = 1000 # number of time steps
39 dt = 0.05 # time step
40 t = np.arange(0, n*dt, dt)
41
42 # intialization of data arrays for numerical solutions
43 x_fe = np.zeros(n)
44 v_fe = np.zeros(n)
45
46 # initial data for t = 0
47 x_fe[0], v_fe[0] = x0, 0
48
49 # numerical integration using the forward Euler method
50 # parameters are passed as variadic arguments
51 for i in range(n-1):
52 x_fe[i+1], v_fe[i+1] = \
53 euler_forward(xddot, t[i], x_fe[i], v_fe[i], dt,
54 m, d, k)
The difficulty in writing a numerical solver in a generic way is that functions such as
xddot() can have any number of parameters. In the case of the damped oscillator,
these parameters are m, d, and k. For some other system, there might be fewer or more
parameters or none at all. We faced the same problem when integrating the Planck
spectrum in Sect 3.2.2. In this case, we used a Python lambda to convert a function
with an additional parameter (the effective temperature) to a proxy function with a
single argument (the wavelength, which is the integration variable). This maintains
120 4 Solving Differential Equations
a clear interface since all arguments are explicitly specified both in the definition
and in the calls of a function. A commonly used alternative is argument packing.
In our implementation of the forward Euler scheme, *args is a placeholder for
multiple positional arguments that are not explicitly specified as formal arguments
of the function. In other words, we can pass a varying number of arguments whenever
the function is called.9 For this reason, they are also called variadic arguments. You
have already come across variadic arguments in Sect. 3.1.1. In the example above,
m, d, and k are variadic arguments of euler_forward(). Why do we need them
here? Because xddot() expects these arguments and so we need to to pass them
to its counterpart f() inside euler_forward() (see line 53–54 and line 29 in
the definition above).
The for loop beginning in line 51 iterates positions and velocities and stores the
values after each time step in the arrays x_fe and v_fe. The arrays are initialized
with zeros for a given number of time steps (lines 43–44). The time array t defined in
line 40 is not needed for numerical integration, but for plotting the solution. The result
is shown as dashed line in Fig. 4.4 (the code for producing the plot is listed below, after
discussing further integration methods). Compared to the analytic solution for the
parameters chosen in our example (solid line), the oscillating shape with gradually
decreasing amplitude is reproduced, but the decline is too slow.
Before we tackle this problem, let us first take a look at the computation of the
analytic solution. For x(0) = x0 and ẋ(0) = 0, it can be expressed as
γ
x(t) = x0 e−γ t cos(ωt) + sin(ωt) , (4.23)
ω
9 Technically speaking, the arguments are packed into a tuple whose name is args (you can choose
any other name). This tuple is unpacked into the individual arguments via the unpacking operator *.
To see the difference, you can insert print statements for both args and *args in the body of
euler_forward() and execute a few steps.
4.1 Numerical Integration of Initial Value Problems 121
Fig. 4.4 Numerical and analytic solutions of differential equation (4.19) for a damped harmonic
oscillator with m = 1, d = 0.05, k = 0.5, x0 = 10, and ẋ0 = 0 (top plot). The relative deviations
from the analytic solution are shown in the bottom plot
64
65 returns: positions (unit amplitude)
66 """
67 try:
68 if m > 0 and d > 0 and k > 0: # positive parameters
69 gamma = 0.5*d/m
70 omega0 = np.sqrt(k/m)
71 if omega0 >= gamma: # underdamped or critical
72 # frequency of damped oscillation
73 omega = np.sqrt(omega0**2 - gamma**2)
122 4 Solving Differential Equations
The relative deviation from the solution computed with the forward Euler scheme
is stored in dev_fe and plotted in Fig. 4.4. The accuracy of the numerical solution
is clearly not convincing. The average error increases from a few percent to almost
100 % after a few oscillations.
How can we improve the numerical solution? For sure, a smaller time step will
reduce the error (it is left as an exercise, to vary the time step and check how this
influences the deviation from the analytic solution). But we can also try to come
up with a better scheme. It turns out that a small modification of the Euler scheme
is already sufficient to significantly improve the solution for a given time step. The
forward Euler scheme approximates xn+1 and ẋn+1 at time tn + t by using the
slopes ẋn and f (tn , xn , ẋn ) at time tn . This is an example for an explicit scheme:
4.1 Numerical Integration of Initial Value Problems 123
values at later time depend only on values at earlier time. What if we used the new
velocity ẋn+1 rather than ẋn to calculate the change in position? In this case, the
iteration scheme is semi-implicit:
This scheme is also called symplectic Euler method and still a method of first
order. Nevertheless, Fig. 4.4 shows a significant improvement over the forward Euler
method. Most importantly, the typical error does not increase with time. This is a
property of symplectic solvers.10 Moreover, the relative error is smallest near the
minima and maxima of x(t). Owing to divisions by small numbers, the deviation has
peaks close to the zeros of x(t). Basically, this causes only small shifts of the times
where the numerical solution crosses x = 0.
Numerical mathematicians have come up with much more complex methods
to achieve higher accuracy. For example, the Runge-Kutta scheme discussed in
Sect. 4.1.1 can be extended to second-order differential equations. Moreover, it can
be generalized to a class of explicit fourth-order methods, which are known as Runge-
Kutta-Nyström (RKN4) methods:
4
ẋn+1 = ẋn + t ċi f ni , (4.26)
i=0
4
xn+1 = xn + t ẋn + t 2 ci f ni , (4.27)
i=0
where ci and ċi are method-specific coefficients and f i are evaluations of the accel-
eration functions (4.13) at times, positions, and velocities given by
10 The term symplectic originates from Hamiltonian systems. In fact, the reformulation of the second-
order differential equation (4.19) as a set of equations for position and velocity is similar to Hamil-
ton’s equations. Symplectic integrators are phase space preserving. This is why the accumulated
error does not grow in time.
124 4 Solving Differential Equations
Is a numerical scheme as complicated as RKN4 worth the effort? Let us take a look
a the solutions plotted with the following code (see Fig. 4.4):
115 import matplotlib.pyplot as plt
116 %matplotlib inline
117
118 T = 2*np.pi/7.066647e-01 # period
119
120 fig = plt.figure(figsize=(6, 4), dpi=100)
121 plt.plot(t/T, x, linestyle=’-’ , color=’red’ ,
122 label="analytic")
123 plt.plot(t/T, x_fe, linestyle=’--’ , color=’orange’ ,
124 label="Euler forward")
125 plt.plot(t/T, x_se, linestyle=’--’ , color=’green’ ,
126 label="Euler symplectic")
127 plt.plot(t/T, x_rkn4, linestyle=’--’ , color=’mediumblue’ ,
128 label="RKN4")
129 plt.legend(loc=’upper right’)
130 plt.xlabel("$t$")
131 plt.ylabel("$x$")
132 plt.savefig("oscillator.pdf")
133
134 fig = plt.figure(figsize=(6, 4), dpi=100)
135 plt.semilogy(t/T, dev_fe, linestyle=’-’, color=’orange’,
136 label=’Euler forward’)
137 plt.semilogy(t/T, dev_se, linestyle=’-’, color=’green’,
138 label=’Euler symplectic’)
139 plt.semilogy(t/T, dev_rkn4, linestyle=’-’ , color=’mediumblue’,
140 label=’RKN4’)
141 plt.legend(loc=’right’)
142 plt.xlabel("$t$")
143 plt.ylabel("deviation")
144 plt.savefig("oscillator_delta.pdf")
We have already discussed the two variants of the Euler method (forward and sym-
plectic). If we just look at the plot showing x(t) for the different solvers, it appears
that we do not gain much by using RKN4 instead of the symplectic Euler solver.
However, the relative deviations reveal that the accuracy of the solution improves
by more than six orders of magnitude with the forth-order Runge-Kutta-Nyström
method. Although this method is quite a bit more complicated than the Euler
method, it requires only five function evaluations vs one evaluation. As explained
in Appendix B.1, you can apply the magic command %timeit to investigate how
execution time is affected. Apart from that, one can also see that the errors are phase
shifted compared to the oscillation (i.e. minimal and maximal errors do not coincide
with extrema or turning points of x(t)) and the error gradually increase with time.
In fact, Runge-Kutta-Nyström solvers are not symplectic. As always in numerics,
you need to take the requirements of your application into consideration and weigh
computational costs against gains.
126 4 Solving Differential Equations
Exercises
ẋ = kx(1 − x) , (4.30)
1
x(t) = . (4.31)
1 − e−kt 1
x(0)
−1
This function has far reaching applications, ranging from population growth over
chemical reactions to artificial neuronal networks. Compute numerical solutions of
equation (4.30) for k = 0.5, k = 1, and k = 2. Vary the time step in each case and
compare to the analytic solution. Suppose the relative error at the time t = 10/k
should not exceed 10−3 . How many time steps do you need? Plot the resulting graphs
for the three values of k.
4.2 Plot the propagation speed ṙs of the Strömgren sphere discussed in Sect. 4.1.1
relative to the speed of light (i.e. ṙs /c) in the time interval 0 < t/ts ≤ 2. Which
consequences do you see for the validity of the model at early time?
4.3 Study the dependence of the error on the time step for the differential equa-
tion (4.19), assuming the same initial conditions and parameters as in the example
discussed above. Start with an initial time step t = T /4, where T = 2π/ω is the
period of the damped oscillation and numerically integrate the initial value problem
over two periods. Iteratively decrease the time step by a factor of two until the relative
deviation of x(t = 2T ) from the analytic solution becomes less than 10−3 . List the
time steps and resulting deviations in a table for the symplectic Euler and RKN4
methods. Compare the time steps required for a relative error below 10−3 . Judging
from the order of the truncation error, what is your expectation?
G M⊕
r̈ = − (4.32)
r2
or in terms of the vertical height h = r − R⊕ above the surface:
G M⊕
ḧ = − (4.33)
(R⊕ + h)2
11 Onekilogram of the chemical explosive TNT releases an energy of 4184 kJ. The most powerful
nuclear weapons ever built produced explosions of several 10 megatons.
128 4 Solving Differential Equations
G M⊕ 1
ḧ = − + ρair (h)CD Aḣ 2 (4.35)
(R⊕ + h) 2 2m
where A = π R 2 is the cross section. In addition to the gravity of Earth (first term
on the right-hand side), the asteroid experiences air resistance proportional to the
square of its velocity (this law applies to fast-moving objects for which the air flowing
around the object becomes turbulent). The asteroid is assumed to be spherical with
radius R. The drag coefficient for a spherical body is CD ≈ 0.5. The dependence of
the density of Earth’s atmosphere on altitude can be approximated by the barometric
height formula:
ρair (h) = 1.3 kg/m3 exp(−h/8.4 km) . (4.36)
1 import numpy as np
2 import astropy.units as unit
3 from astropy.constants import G,M_earth,R_earth
4
5 # asteroid parameters
6 R = 34*unit.m # radius
7 V = (4*np.pi/3) * R**3 # volume
8 rho = 2.2e3*unit.kg/unit.m**3 # density
9 m = rho*V # mass
As start point for the numerical integration of the initial value problem, we choose
the height h 0 = 300 km. The internal space station (ISS) orbits Earth 400 km above
ground. The density of Earth’s atmosphere is virtually zero at such altitudes. As a
result, we can initially neglect the drag term and calculate the initial velocity v0 using
Eq. (4.34):
1 h0 = 300*unit.km
2 v0 = np.sqrt(2*G*M_earth/(R_earth + h0))
To apply our Runge-Kutta integrator from numkit, we need to express the second
derivative, ḧ, as function of t, h, and ḣ. In the following code, a Python function with
the formal arguments required by rkn4_step() and additional parameters for the
mass and radius of the asteroid is defined (see Sect. 4.1.2).
4.2 Radial Fall 129
The array data is initialized in line 21 with the initial values, which constitute the
first row of a two-dimensional array.12 Variables for the iteration of t, h, and ḣ are
defined in line 24, where tuple() is used to convert the row data[0] into a
tuple, which can be assigned to multiple variables in a single statement. The while
loop starting at line 31 calls rkn4_step() for the time step dt until h becomes
negative. Go through the arguments in lines 32–33 and check their counterparts in the
definition of rkn4_step(). After incrementing time, the updated variables h and
hdot are appended to the data array in line 35. We have already used the function
np.append() to append elements to a one-dimensional array (see Sect. 3.1.2).
In the case of a two-dimensional array, the syntax is more tricky because there are
different possibilities of joining multi-dimensional arrays. Here, we want to append
a new row. Similar to the initialization in line 21, the row elements have to be
collected in a two-dimensional array (as indicated by the double brackets) which is
then merged into the existing array. To that end, it is necessary to specify axis=0.
This axis is in the direction running through rows of an array (corresponding to the
first array index) and, thus, indicates that rows are to be appended to rows. If axis=0
is omitted, np.append() will flatten the resulting array, i.e. it is converted into a
one-dimensional array with all elements in a sequence (see for yourself what happens
to the data array without specifying the axis).13 Since we do not know the number of
time steps in advance, it is convenient to build the data array row by row. However,
this means that a new array is created and the complete data from the previous array
have to be copied for each iteration (the function’s name append is somewhat
misleading in this regard). For large arrays, this requires too much time and slows
down performance of the code. We will return to this issue in Sect. 4.4.
The output produced by the code listed so far is the acceleration ḧ at time t = 0:
Its absolute value is close to Earth’s surface gravity g = 9.81 m/s2 . To see how the
motion progresses in time, we plot h(t) as a function of t and ḣ vs h:
36 import matplotlib.pyplot as plt
37
38 plt.figure(figsize=(12,4), dpi=100)
39
40 plt.subplot(121)
41 plt.plot(data[:,0], 1e-3*data[:,1])
42 plt.xlabel("$t$ [s]")
43 plt.ylabel("$h$ [km]" )
44
45 plt.subplot(122)
12 Strictly speaking, the expression with double brackets is a Python list, not a NumPy array. But
shaped. A simple example can be found in the notebook for this section.
4.2 Radial Fall 131
46 plt.plot(1e-3*data[:,1], -1e-3*data[:,2])
47 plt.xlim(h0.value+10,-10)
48 plt.xlabel("$h$ [km]")
49 plt.ylabel("-$\dot{h}$ [km/s]" )
50 plt.savefig("asteroid.pdf")
The plot particular variables we need to collect elements column-wise. In Python,
there is a simple mechanism for extracting multiple elements from an array, which
is called slicing. A slice of an array is equivalent to an index range. For example,
data[0,1:2] extracts the two elements with row index 0 and column index run-
ning from 1 to 2 (these elements are the height and velocity at time t = 0). To obtain
the data for the first 10 time steps, you would use the expression data[0:9,:].
If no start and end numbers are specified, the index simply runs through all possible
numbers, in this case, from 0 to 2. The first column of the data array, i.e. all time
values, is expressed as data[:,0]. This is the first argument of plt.plot()
in line 41. Can you identify the slices referring to other variables in the example
above? Furthermore, you might find it instructive to experiment with arbitrary slices
by printing them.
The plots shown in Fig. 4.5 are produced with the help of plt.subplot(). To
arrange two plots in a row, the left subplot is positioned with plt.subplot(121)
and the right subplot with plt.subplot(122), where the first digit indicates the
number of plots in vertical direction, the second is the number of plots in a row, and
the third enumerates the subplots starting from 1. At first glance, the numerically
computed solution might appear surprising. The height h(t) is nearly linear although
the asteroid is in free fall. The reason is that the free-fall velocities at radial distances
R⊕ + h 0 and R⊕ differ only by a small fraction since the initial height h 0 is small
compared to Earth’s radius R⊕ and the asteroid has gained most of its velocity in the
long infall phase prior to our arbitrarily chosen initial point t = 0. The range along
the horizontal axis of the right subplot (velocity vs height) is reverted (see line 47).
As a result, the height decreases from left to right corresponding to the progression
Fig. 4.5 Vertical motion of an asteroid of radius R = 34 m through Earth’s atmosphere. The left
plot shows the altitude h above ground as a function of time. The relation between the asteroid’s
velocity ḣ and h is shown in the right plot
132 4 Solving Differential Equations
of time. As long as the asteroid is nearly in free fall, |ḣ| increases gradually, but then
the downward speed suddenly drops when the asteroid passes through the lower and
much denser layers of the atmosphere. This final phase lasts only a few seconds.
The heating due to air friction equals minus the rate at which kinetic energy is
dissipated per unit time. It can be calculated by multiplying the second term in the
expression for ḧ with the mass m and velocity ḣ (i.e. drag force times velocity):
51 def dissipation(h, hdot, m, R):
52 return -0.5*rho_air(h)*c_drag * np.pi*R**2 * hdot**3
The dissipation rate is plotted against height in Fig. 4.6 (the code producing this
plot can be found in the notebook for this chapter). In the lower layers of Earth’s
atmosphere, the asteroid decelerates and heating rises dramatically.
How much energy does the asteroid lose in total before it hits the ground? To
answer this question, we need to integrate the dissipation rate:
t
CD A
E diss (t) = − ρair (h)ḣ 3 dt . (4.37)
2 0
Since the data for h and ḣ are discrete, we can evaluate the function we want to
integrate only for t = nt (n = 0, 1, 2, . . .), but not for arbitrary t. For this reason,
we need to modify the numerical integration routines from Chap. 3. The following
version of the trapezoidal rule is applicable to an array of function values:
4.2 Radial Fall 133
0.9979429863543572
NumPy offers an equivalent library function for numerical integration using the
trapezoidal rule:
69 np.trapz(y, dx=(b-a)/n)
The result agrees within machine precision:
0.9979429863543573
To compute the fraction of kinetic energy that is dissipated by air resistance, we first
extract the data for h and ḣ by slicing the full data array. These data are used as
input to compute discrete values of the dissipation rate, which are in turn integrated
with dt as subinterval width:
70 energy_diss = integr_trapez(
71 dissipation(data[:,1], data[:,2],
134 4 Solving Differential Equations
In absolute terms, this is a sizable amount of energy. Since most of this energy is
released within a few seconds, it is plausible that the rapid heating may cause the
asteroid to disintegrate and burn up, as it likely happened in the Tunguska event.
In Exercise 4.5 you can investigate the much stronger impact of air resistance on
smaller objects called meteoroids.
Exercises
4.4 Rewrite Simpson’s rule (see Sect. 3.2.2) for an array of function values. Compute
the dissipated energy for the asteroid discussed in this section and compare to the
result obtained with the trapezoidal rule.
4.5 The influence of air resistance increases for smaller objects. Solve the differ-
ential equation (4.35) for a meteoroid of radius R = 25 cm (rocky objects of size
smaller than about one meter are called meteoroids rather than asteroids; when mete-
oroids enter Earth’s atmosphere, they become visible as meteors and their remnants
on ground are called meteorites).
1. Plot altitude and velocity of the meteoroid along with the asteroid data from the
example above. Interpret the differences.
2. Print the impact time and speed.
3. Estimate roughly when the falling meteoroid becomes a meteor (physically speak-
ing, a meteor is a meteoroid heated to high temperatures by friction).
4.6 The Falcon 9 rockets produced by SpaceX14 are famous for their spectacu-
lar landing via a so-called suicide burn. After deploying the payload at the target
orbit, the rocket performs a maneuver that brings it back into Earth’s atmosphere
where the atmospheric drag controls its maximum velocity. Assuming an equilib-
rium between gravity and air resistance, the downward acceleration ḧ approaches
zero and Eq. (4.35) implies that the terminal velocity for an object of mass m, cross-
section A, and drag coefficient cD is given by
2 gm
vmax = , (4.38)
ρcD A
14 See www.spacex.com/falcon9.
4.2 Radial Fall 135
where g is the gravitational acceleration. For instance, in the case of the first stage
of a Falcon 9 Full Thrust Block 5 rocket, the cross-section is nearly circular with a
radius of R = 1.83 m, a mass of m d = 27200 kg (dry mass without propellant), and
CD ≈ 0.5.
1. How fast would the rocket hit the sea if it descends to sea level (h = 0)?
2. For a safe touchdown, the velocity must be limited to a few meters per second,
which is much less than the terminal velocity resulting from atmospheric drag.
The rocket fires its thrusters to eliminate any lateral motion before it performs a
landing burn to decelerate in vertical direction. Since burning propellant reduces
not only the velocity but also the mass of the rocket, which in turn changes the
drag term, the computation is more complicated than in the case of an asteroid.
For a constant propellant burning rate b, the time-dependent rocket mass can be
written as
m(t) = m d + m p − b t (4.39)
where m p is the total propellant mass and m d the dry mass of the rocket. The
modified height equation for a thrust T produced by the rocket engines reads
G M⊕ 1 T
ḧ = − + ρair (h)CD Aḣ 2 − . (4.40)
(R⊕ + h)2 2 m(t) m(t)
Modify the Python function hddot() defined above such that the ignition thrust
term −T /m(t) is added for t ≥ tignite , where tignite is the ignition time. Include
additional parameters in the argument list.
3. Assume h 0 = 150 km and ḣ 0 = 2 km/s as initial data (t = 0). Define an array of
ignition times ranging from 50 to 70 s with 0.5 s spacing. For each ignition time,
solve Eq. (4.40) for T = 7.6 × 103 kN, m p = 3.0 × 104 kg, and b = 1480 kg/s.
Terminate integration if h = 0 is reached or the propellant is exhausted. Determine
the height at which the velocity ḣ switches signs from negative (descent) to
positive (ascend) as a function of tignite and plot the results.
4. To land the rocket safely, the ignition time must be adjusted such that ḣ is nearly
zero just at sea level. Estimate the optimal ignition time from the plot (it might be
helpful to use plt.xlim() to narrow down the range) and interpolate between
the nearest date points. Determine the touchdown time, speed, and remaining
mass of propellant for the interpolated ignition time.
4.7 How long does it take to fall into a black hole? Well, the answer depends quite
literally on the point of view. Suppose a spaceship is plunging from an initial radial
distance r0 toward a black hole of mass M. For simplicity, we assume that the
spaceship has zero initial velocity and is accelerated by the gravitational pull of the
black hole. We also neglect any orbital angular momentum. Such initial conditions are
unrealistic, but our assumptions will suffice for argument’s sake. The radial position
r at time t is determined by the following differential equation [12]:
136 4 Solving Differential Equations
dr RS RS RS 1/2 RS −1/2
= −c 1 − − 1− , (4.41)
dt r r r0 r0
where c is the speed of light and RS = 2G M/c2 is the Schwarzschild radius of the
black hole. In this exercise, we will consider two scenarios: (a) a stellar black hole of
mass M = 10 M and (b) the supermassive black hole at the center of the Milkyway
with M ≈ 4 · 106 M . As you can see from the right-hand side of the Eq. (4.41), RS
plays a crucial role. Solve the initial value problem for r0 = 100RS and plot r (t).
You will find that the spaceship never crosses the sphere with radius r = RS , which
is called the event horizon of the black hole, but appears to hover just above the
horizon for eternity.
So is there actually nothing to be feared by someone on board of the spaceship,
except being captured in the vicinity of the black hole? In relativity, the progression
of time (i.e. the time interval measured by a clock) depends on the observer’s frame
of reference. The solution following from Eq. (4.41) is what a distant observer far
away from the black hole will witness, where the effects of gravity are negligible. For
the crew of the spaceship, however, a dramatically different chain of events unfolds.
What they read on the starship’s clocks is called proper time τ and, for the crew, the
rate of change of radial position is given by15
1/2
dr RS RS
= −c − . (4.42)
dτ r r0
Solve this equation for the same initial conditions as above. At which time would
the spaceship cross the event horizon of the stellar and the supermassive black hole?
In the case of the stellar black hole the spaceship will be torn apart by tidal forces
even before it reaches the horizon (see Exercise 2.12). Putting aside tidal effects, how
long will it take the spaceship until it finally gets crushed by the singularity at r = 0?
The completely different outcome for the crew of the spaceship is a consequence of
gravitational time dilation, which becomes extreme in the case of a black hole.
The motion of a planet and a star or two stars around the common center of mass (in
astronomy also called barycenter) is governed by Kepler’s laws. The properties of
the orbits are determined by integrals of motion such as the total energy and orbital
angular momentum. Alternatively, we can solve the equations of motion directly.
For systems composed of more than two bodies interacting with each other, there
is no other way than numerical integration. To begin with, we will apply different
15 In the non-relativistic limit, τ t and you can derive Eq. (4.42) from energy conservation E kin +
E pot = 0.
4.3 Orbital Mechanics 137
M2 M1
r1 = − d, r2 = d, (4.43)
M1 + M2 M1 + M2
where d = r2 − r1 is the distance vector between the two stars. The accelerations
are given by
F12 F21
r̈1 = , r̈2 = , (4.44)
M1 M2
G M1 M2
F12 = −F21 = d. (4.45)
d3
To define initial conditions, we make use of the vis-viva equation:
2 1
v = G (M1 + M2 )
2
− , (4.46)
d a
where v is the modulus of the relative velocity ḋ, G is Newton’s constant, and a
is the semi-major axis of the motion of the distance vector d(t). Remember that
the two-body problem can be reduced to a one-body problem for a body of mass
μ = M1 M2 /(M1 + M2 ) moving along an elliptic orbit given by d(t). At the points
of minimal and maximal distance, the velocity vector ḋ is perpendicular to d. At the
periastron, where the two stars are closest to each other, we have
M2
r1 (0) = dp , 0 , 0 , (4.47)
M1 + M2
M1
r2 (0) = − dp , 0 , 0 , (4.48)
M1 + M2
assuming that the major axes of the ellipses are aligned with the x-axis. For ellipses of
eccentricity e, the periastron distance is given by dp = a(1 − e) and the correspond-
ing relative velocity, vp , is obtained by substituting dp into Eq. (4.46). By orienting
the z-axis perpendicular to the orbital plane, the orbital velocities at the periastron
can be expressed as
138 4 Solving Differential Equations
M2
v1 (0) ≡ ṙ1 (0) = 0 , − vp , 0 , (4.49)
M1 + M2
M1
v2 (0) ≡ ṙ2 (0) = 0 , vp , 0 . (4.50)
M1 + M2
This completes the initial value problem for the two stars.
To proceed with Python, we first define masses and orbitial parameters:
1 import numpy as np
2 from scipy.constants import G,year,au
3 from astropy.constants import M_sun
4
5 M1 = 2.06*M_sun.value # mass of Sirius A
6 M2 = 1.02*M_sun.value # mass of Sirius B
7
8 a = 2.64*7.4957*au # semi-major axis
9 e = 0.5914
Sirius B is a white dwarf of about one solar mass and Sirius A a more massive
main-sequence star. In line 8, the semi-major axis a ≈ 20 AU is calculated from the
distance of the star system from Earth and its angular size. The orbital eccentricity
of about 0.6 indicates a pronounced elliptical shape. The orbital period follows from
Kepler’s third law:
1 T = 2*np.pi * (G*(M1 + M2))**(-1/2) * a**(3/2)
2
3 print("Orbital period = {:.1f} yr".format(T/year))
Since Sirius A and B orbit each other at relatively large distance, they need years to
complete one orbital revolution:
So far, we have solved differential equations for a single function (e.g. the displace-
ment of an oscillator in Sect. 4.1 and the radial coordinate in Sect. 4.2). In the case
of the two-body problem, we are dealing with a system of coupled differential equa-
tions (4.44) for the vector functions r1 (t) and r2 (t). By reformulating the equations
of motion as a system of first-order differential equations,
G M2 G M1
v̇1 = (r2 − r1 ) , v̇2 = (r1 − r2 ) , (4.51)
|r2 − r1 |3 |r2 − r1 |3
ṙ1 = v1 , ṙ2 = v2 , (4.52)
orientation of the coordinate frame is chosen such that the orbits are in the x y-plane
and z-coordinates can be ignored):
4 n_rev = 3 # number of revolutions
5 n = n_rev*500 # number of time steps
6 dt = n_rev*T/n # time step
7 t = np.arange(0, (n+1)*dt, dt)
8
9 # data arrays for coordinates
10 x1 = np.zeros(n+1)
11 y1 = np.zeros(n+1)
12 x2 = np.zeros(n+1)
13 y2 = np.zeros(n+1)
14
15 # data arrays for velocity components
16 vx1 = np.zeros(n+1)
17 vy1 = np.zeros(n+1)
18 vx2 = np.zeros(n+1)
19 vy2 = np.zeros(n+1)
Before proceeding with the numerical integration, we need to assign the initial con-
ditions (4.47) to (4.50) to the first elements of the data arrays:
20 # periastron distance and relative velocity
21 d = a*(1 + e)
22 v = np.sqrt(G*(M1 + M2)*(2/d - 1/a)) # vis-viva eq.
23
24 x1[0], y1[0] = d*M2/(M1 + M2), 0
25 x2[0], y2[0] = -d*M1/(M1 + M2), 0
26
27 vx1[0], vy1[0] = 0, -v*M2/(M1 + M2)
28 vx2[0], vy2[0] = 0, v*M1/(M1 + M2)
Time integration is implemented in the following for loop through all time steps:
29 alpha = G*M1*M2
30
31 for i in range(n):
32
33 delta_x = x2[i] - x1[i]
34 delta_y = y2[i] - y1[i]
35
36 # third power of distance
37 d3 = (delta_x**2 + delta_y**2)**(3/2)
38
39 # force components
40 Fx = alpha*delta_x/d3
140 4 Solving Differential Equations
41 Fy = alpha*delta_y/d3
42
43 # forward Euler velocity updates
44 vx1[i+1] = vx1[i] + Fx*dt/M1
45 vy1[i+1] = vy1[i] + Fy*dt/M1
46 vx2[i+1] = vx2[i] - Fx*dt/M2
47 vy2[i+1] = vy2[i] - Fy*dt/M2
48
49 # forward Euler position updates
50 x1[i+1] = x1[i] + vx1[i]*dt
51 y1[i+1] = y1[i] + vy1[i]*dt
52 x2[i+1] = x2[i] + vx2[i]*dt
53 y2[i+1] = y2[i] + vy2[i]*dt
Fig. 4.7 Numerical solution of the two-body problem for the binary stars Sirius A and B computed
with the forward Euler method. The center of mass is located at the origin (black cross)
Figure 4.7 shows the resulting orbits (the Python code producing the plot is listed
below). Although the shape resembles an ellipse, they solution is clearly not correct.
The two stars are gradually drifting outwards and their apastrons (most distant points)
are not at the x-axis. As a result, there are no closed orbits. This is in contradiction
with the analytic solution of the two-body problem.
54 import matplotlib.pyplot as plt
55 %matplotlib inline
56
57 fig = plt.figure(figsize=(6, 6*35/55), dpi=100)
58
59 plt.plot([0], [0], ’+k’) # center of mass
60 plt.plot(x1/au, y1/au, color=’red’, label=’Sirius A’)
4.3 Orbital Mechanics 141
Fig. 4.8 Same as in Fig. 4.7, but computed with a semi-implicit scheme
142 4 Solving Differential Equations
different numerical integrators.16 Before applying this function, you need to famil-
iarize yourself with the notion of a state vector. Suppose we have a system of ODEs
for N functions sn (t), where n ∈ [1, N ]:
ṡ1 = f 1 (t, s1 , . . . , s N ) ,
..
. (4.53)
ṡ N = f N (t, s1 , . . . , s N ) . (4.54)
ṡ = f(t, s) , (4.55)
It does not matter how the variables are ordered in the state vector, but it is helpful
to use mnemonic ordering such that the index n can be easily associated with the
corresponding variable. Generally, each component f n of the right-hand side of
Eq. (4.55) may depend on all or any subset of the variables s1 , . . . , s N . For example,
f 1 is only a function of s5 = v1x , while f 8 depends on s1 = x1 , s2 = y1 , s3 = x2 , and
s4 = y2 (see definition of the gravitational force (4.45)).
In Python, state vectors can be defined as NumPy arrays. For example, the initial
state s(0) is given by the following array:
54 from scipy.integrate import solve_ivp
55
56 init_state = np.array([ x1[0], y1[0], x2[0], y2[0],
57 vx1[0], vy1[0], vx2[0], vy2[0]])
The array elements (initial positions and velocities of the stars) are defined in lines
24–28. To apply solve_ivp(), we need to define a Python function that evaluates
the right-hand side of Eq. (4.55):
16 See docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html.
4.3 Orbital Mechanics 143
88 plt.legend(loc=’upper left’)
89 plt.savefig("sirius_scipy.pdf")
The plot is shown in Fig. 4.9. It turns out the orbits are note quite elliptical. The
semi-major axes is slowly shrinking. You might find this surprising, as the docu-
mentation shows that solve_ivp() applies a higher-order Runge-Kutta scheme
by default. However, the time step and error tolerances are adjusted such that the
integrator computes the solution efficiently with a moderate number of time steps. It
is also important to keep in mind that Runge-Kutta methods are not symplectic (see
Sect. 4.1.2). Therefore, the error can grow with time. The lesson to be learned here is
that numerical library functions have to be used with care. They can be convenient
and may offer more sophisticated methods than what you would typically program
yourself. But to some degree you need to be aware of their inner workings and tuning
parameters to avoid results that do not meet your expectation. In Exercise 4.8, you
can further explore solve_ivp() and learn how to improve the accuracy of the
orbits of Sirius A and B.
To conclude our discussion of orbital mechanics, we will solve a special case of
the three-body problem, where two objects are in a close, inner orbit. Together with
a third, more distant object, they follows an outer orbit around the common center
of mass. Such a configuration is found in the triple star system Beta Persei, which is
also known as Algol. Algol Aa1 and Aa2 constitute an eclipsing binary with a period
Fig. 4.9 Same as in Fig. 4.8, but computed with the initial value problem solver from SciPy (fifth-
order Runge-Kutta scheme with fourth-order error estimator)
4.3 Orbital Mechanics 145
of less than three days.18 This binary and a third star, designated Ab, revolve around
each other over a period of 680 days. Here are the orbital parameters of the Algol
system:
1 from scipy.constants import day
2
3 M1 = 3.17*M_sun.value # mass of Algol Aa1
4 M2 = 0.70*M_sun.value # mass of Algol Aa2
5 M3 = 1.76*M_sun.value # mass of Algol Ab
6
7 # inner orbit (Aa1 and Aa2)
8 T12 = 2.867*day
9 e12 = 0
10
11 # outer orbit (Aa and Ab)
12 T = 680.2*day
13 e = 0.227
Since the orbital periods are known with higher precision, we compute the semi-
major axes of the inner and outer orbits using Kepler’s third law, assuming that the
binary Aa and the star Ab can be treated as a two-body system with masses M1 + M2
and M3 . For the inner orbit (Aa1 and Aa2), the third star can be ignored, similar to
the Earth-Moon system and the Sun:
14 from scipy.constants import day
15
16 a12 = (T12/(2*np.pi))**(2/3) * (G*(M1 + M2))**(1/3)
17 a = (T/(2*np.pi))**(2/3) * (G*(M1 + M2 + M3))**(1/3)
18
19 print("Inner semi-major axis = {:.2e} AU".format(a12/au))
20 print("Outer semi-major axis = {:.2f} AU".format(a/au))
As expected, the size of the inner orbit is much smaller than the outer orbit (less ten
10 million km vs roughly the distance from the Sun to Mars).
18 The term eclipsing binary means that the orbital plane is nearly parallel to the direction of the line
of sight from Earth. For such a configuration, one star periodically eclipses the other star, resulting
in a characteristic variation of the brightness observed on Earth.
146 4 Solving Differential Equations
26 x2, y2 = -d12*M1/M12, 0
27
28 vx1, vy1 = 0, -v12*M2/M12
29 vx2, vy2 = 0, v12*M1/M12
For the next step, think of Aa1 and Aa2 as a single object of total mass M1 + M2
(variable M12) positioned at the binary’s center of mass. By treating the binary and
Ab in turn as a two-body system, we have
30 d = a*(1 - e)
31 v = np.sqrt(G*(M12 + M3)*(2/d - 1/a))
32
33 x1 += d*M3/(M12 + M3)
34 x2 += d*M3/(M12 + M3)
35
36 x3, y3 = -d*M12/(M12 + M3), 0
37
38 vy1 -= v*M3/(M12 + M3)
39 vy2 -= v*M3/(M12 + M3)
40
41 vx3, vy3 = 0, v*M12/(M12 + M3)
In lines 33–34, the x- and y-coordinates are shifted from the center-of-mass frame of
Aa1 and Aa2 to the center-of-mass frame for all three stars. The Keplerian velocities
at the periastron of the outer orbit are added to the velocities from above (lines 38–
39), while Ab moves only with the outer orbital velocity (line 41). As you can see,
two further assumptions are applied here. First of all, all three stars are assumed to
be simultaneously at their periastrons at time t = 0. Generally, periastrons of the
inner and outer orbits do not coincide. Second, the plane of the outer orbit of Algol is
inclined relative to the plane of the inner orbit. As a result, it would be necessary to
treat the motion of the three stars in three-dimensional space. For simplicity’s sake,
we will ignore this and pretend that the orbits are co-planar.
We solve the equations of motion for the full three-body interactions, i.e. the
resulting force acting on each star is given by the sum of the gravitational fields of
the other two stars. For example, the equation of motion of Algol Aa1 reads
F12 + F23
r̈1 = , (4.57)
M1
where
G M1 M2 G M1 M3
F12 = 3
d12 , F13 = 3
d13 , (4.58)
d12 d13
Analogous equations apply to Algol Aa2 and Ab. In terms of the state vector of the
system, which has twelve components (six positional coordinates and six velocity
components), these equations can be implemented as follows.
42 def state_derv(t, state):
43 alpha = G*M1*M2
44 beta = G*M1*M3
45 gamma = G*M2*M3
46
47 delta12_x = state[2] - state[0] # x2 - x1
48 delta12_y = state[3] - state[1] # y2 - y1
49
50 delta13_x = state[4] - state[0] # x3 - x1
51 delta13_y = state[5] - state[1] # y3 - y1
52
53 delta23_x = state[4] - state[2] # x3 - x2
54 delta23_y = state[5] - state[3] # y3 - y2
55
56 # force components
57 F12x = alpha*delta12_x/(delta12_x**2 + delta12_y**2)**(3/2)
58 F12y = alpha*delta12_y/(delta12_x**2 + delta12_y**2)**(3/2)
59
60 F13x = beta*delta13_x/(delta13_x**2 + delta13_y**2)**(3/2)
61 F13y = beta*delta13_y/(delta13_x**2 + delta13_y**2)**(3/2)
62
63 F23x = gamma*delta23_x/(delta23_x**2 + delta23_y**2)**(3/2)
64 F23y = gamma*delta23_y/(delta23_x**2 + delta23_y**2)**(3/2)
65
66 return np.array([state[6], state[7],
67 state[8], state[9],
68 state[10], state[11],
69 ( F12x + F13x)/M1, ( F12y + F13y)/M1,
70 (-F12x + F23x)/M2, (-F12y + F23y)/M2,
71 (-F13x - F23x)/M3, (-F13y - F23y)/M3])
It is left as an exercise to define the initial state vector for the system and to compute
and plot the solution using solve_ivp().19 Figure 4.10 shows the resulting orbits
for the time interval [0, 0.5T ]. Algol Aa1 and Aa2 nearly move like a single object
along a Kepler ellipse. In a close-up view the two stars revolve on much smaller
orbits around their center of mass (see Fig. 4.11). Combined with the center-of-mass
motion, the stars follow helix-like paths. Since the period of the binary’s inner orbit
is much shorter, a sufficiently small time step has to be chosen (for the orbits shown
19 If this is too time consuming for you, the complete code can be found in the notebook and source
in Figs. 4.10 and 4.11 the time step is 0.1 d). This results in a large number of time
steps if the equations of motion are integrated over a time interval comparable to the
period of the outer orbit. Disparate time scales are a common problem in numerical
computation. Often it is not feasible to follow the evolution of a system from the
smallest to the largest timescales. In the case of the Algol system, for example, an
approximative solution would be to solve the two-body problem for the inner and
outer orbits separately. In order to do this, how would you modify the code listed
above?
Exercises
4.8 Compare different solvers in solve_ivp() for the orbits of Sirius A and
B. The solver can be specified with they keyword argument method. Moreover
investigate the impact of the relative tolerance rtol. See the online documentation
for details.
4.9 Compute the motion of a hypothetical planet in the Sirius system. Apply the
test particle approximation to the planet, i.e. neglect the gravity of the planet in
the equations of motion of the stars, while the planets’s motion is governed by the
gravitational forces exerted by Sirius A and B.
(a) Determine initial data from the apastron of a Kepler ellipse of eccentricity =
0.3, assuming a single star of mass M1 + M2 ≈ 3.08 M at the center of mass of
binary. Under which conditions do you expect this to be a good approximation?
Solve the initial value problem for different ratios of the initial distance of the
4.3 Orbital Mechanics 149
planet from the barycenter and the semi-major axis of the binary star (a ≈
20 AU).20 In other words, consider cases where the size of the planetary orbit
is comparable to the distance between the two stars and where the planet moves
far away from the binary. How does binary affect the planet’s orbit over several
periods and in which respect does it differ from a Keplerian orbit?
(b) Now assume that the planet follows a close circular orbit around Sirius A (i.e.
the radius of the orbit is small compared to the separation of the binary). Which
approximation can be applied to initialize the orbit in this case? Successively
increase the orbital radius from about 0.1 AU to larger values. At which distance
from Sirius A destabilizes the planetary orbit within several revolutions (i.e.
begins to deviate substantially from the initial elliptical shape)?
4.10 In general, the three-body problem cannot be solved analytically. In many cases
motions are chaotic, while stable configurations are rare. Consider a system of three
stars, each with a mass of 1 M . Intialize the spatial coordinates and velocities of
20 Another parameter is the relative orientation of the major axes of the orbit of the binary and the
planet’s orbit. You can also vary this parameter if you are interested in its influence.
150 4 Solving Differential Equations
each star with uniformly distributed random numbers. For the coordinates x, y, and z,
generate random numbers in the interval [−1 AU, 1 AU] using the NumPy function
random.uniform(), whose first and second argument are the endpoints of the
interval from which a random number is to be drawn. By setting the third argument
to 3, the function returns three random numbers for the three coordinates. In the
same way, generate random velocity
√ components vx , v y , and vz within the interval
[−vmax , vmax ], where vmax = G M /0.01 AU is the orbital velocity around a solar
mass at a distance of 0.01 AU (this is 10 times the orbital velocity of Earth around
the Sun).
(a) Solve the initial value problem for a time interval of at least 10 yr. Plot the
pairwise distances d12 , d13 , and d23 between the stars as function of time. Repeat
the procedure for several randomly chosen initial conditions and interpret your
results. How can you identify bound orbits? (Think about the time behaviour of
the distance between two stars in a two-body system.)
(b) The displacement vectors d12 , d13 , and d23 form a triangle with the three stars
at the vertices. Calculate the time-dependent internal angles of the triangle by
applying the law of cosines. If the stars are eclipsing (i.e. they are aligned along
a line), the triangle will degenerate into a line. As a result, you can detect
eclipses by tracking the time evolution of the internal angles. If the configu-
ration approaches an eclipse, the smallest and largest angles will be close to 0
and 180◦ , respectively. Can you identify such events in your sample?21
(c) You can draw a triangle that is similar to the displacement tri-
angle (i.e. a triangle with identical internal angles) by utilizing Polygon()
from matplotlib.patches. Position the first star at the origin, align d12
with the horizontal coordinate axis of the plot and place the third vertex of the
triangle such that angles are preserved. What can you deduce from the time
evolution of the shape of the triangle? (Optionally, you can return to this exer-
cise after reading the next section. You will then be able to illustrate the time
evolution of the three-body system by animating the triangle.)
(d) If you want to optimize the performance of your code to compute larger samples,
you can follow the instructions in Appendix B.2.
21 The three-body problem and its rich phenomenology is intriguing for astrophysicists and mathe-
maticians alike. For example, properties of the displacement triangles can be analyzed in an abstract
shape space, which can be mapped to a sphere, the so-called shape sphere. The evolution of any
three-body system is described by a curve on this sphere. Eclipses live on the sphere’s equator. See
[13] and references therein.