Path: blob/main/Lesson 4 - Differential equations.ipynb
968 views
Lesson 4 - Differential equations
Authors:
Yilber Fabian Bautista
Alejandro Cruz-Osorio
Keiwan Jamaly
Sean Tulin
Differential equations are ubiquitous in physics, and moreover come in many different types. Depending on whether you have ordinary or partial differential equations, an initial value problem or boundary value problem, linear or nonlinear equations, and depending on the order, there are different methods and tools for finding solutions.
Here we learn how to solve an initial value problems (IVP) for ordinary differential equations (ODEs). That is, we are solving for function(s) of one independent variable (say, time ), where the boundary condition(s) are imposed at some initial time and we are solving for what happens at later times . The numerical methods described here can be applied to linear or nonlinear systems of ODEs of any order.
There is an existing function scipy.integrate.solve_ivp within the scipy library that does this already. It is recommended that you use this function for all your IVP needs. We also describe some of the theoretical background for numerical solutions to IVPs to provide some greater context for how scipy.integrate.solve_ivp works.
Objectives:
By the end of this lecture you will be able to:
Use the Euler and Runge-Kutta methods to solve numerically IVPs for ODEs.
Solve first-order and higher-order ODEs using
scipy.integrate.solve_ivp.
Introduction: Euler method
Imagine we want to calculate the shape of an unknown curve which starts at a given point at and satisfies a given differential equation. The Euler method is the simplest algorithm for solving such a problem. It approximates the unknown curve as follows:
Starting at the initial value at , from the differential equation we can compute the slope of a tangent line to the curve at , and so, the tangent line itself. We then take a small step along our tangent line until we reach the point at . For a -step small enough, the slope of the curve does not change too much in this -displacement and therefore is still close to the curve. We can then repeat the reasoning as for , that is, taking as our initial value, and from the differential equation compute the slope of a tangent line to the curve at , then move along the new tangent line until reaching at . The process is then repeated until we reach the final time , at which we would like to know the value of the unknown value of the curve . Schematically this is:

Figure taken from here
The Euler method is a first-order method. This means that the local error (or the error per step) is proportional to the square of the step size, whereas the global error (the error at a given ) is proportional to the step size .
What we just described in words can be made precise with equations. Given an ODE and boundary condition
we choose a value for the step size so that for , where in an integer counting how many steps we have taken. After one step in the Euler method, from to , we evaluate by Taylor expanding around :
We replace by the r.h.s. of the differential equation:
or in a index notation
As an example, if the differential equation is
we can set the function to be
Let us now use the method in an specific example
Exercise 1
Use the Euler method previously programed and vary the total number of steps , What happens for different step sizes? Plot your findings using a for loop.
Exercise 2
Generalize the
Euler()function defined above to take as input a general function which is the function in the r.h.s. of the differential equation:
As before, use your new
Euler()function to solve for , with . Your solution should agree with the previous results.Using your new
Euler()function, solve the differential equation with initial condition in the time interval
Runge-Kutta method
The Euler method is a first-order approximation and is less accurate than higher-order methods for a fixed step size. Therefore, to achieve a desired accuracy in a numerical calculation, the Euler method requires many more steps (and therefore much longer computing time) while a higher-order method can be much more efficient.
For better approximations, we can use Runge-Kutta (RK) methods. RK4 is a fourth-order numerical solution, which means that the local error (per step) is and the global error (after all steps) is .
Let's describe how the algorithm works. Here is our ODE and boundary condition:
Similar to the Euler method, you define a step size and then iterate from to to , etc. From each step and , the subsequent step is computed as
where are
It is rather complicated to show that the errors do indeed cancel out up to , so we omit it here.
Note that for each step we have to evaluate four times, whereas for the Euler method we only evaluated once per step. This is a generic feature of higher-order methods: each step requires more function evaluations (more computing time), but because it is more accurate, much fewer steps are required to achieve the same accuracy.
Exercise 3 (optional)
Similar to Exercise 1, create a program
Runge_Kutta()that implements this method for a general input function .Use your Runge_Kutta() function to solve the differential equation with initial condition in the time interval . Plot your findings and compare them with the solution from the Euler method for different t-resolutions
ODE solvers in scipy library
Now that we have gain some intuition on how numeric methods for solving first order differential equations work, we want to take advantage of existing methods in the scipy library. There are two ODE solvers: odeint and solve_ivp. odeint is outdated so we will not show it here, but there are good examples in the documentation and later on in case you are working with old code bases.
solve_ivp
scipy.integrate.solve_ivp implements many methods for solving IVPs, which can be chosen by the user (see documentation).
The default method is RK45 with an adaptive step size that is adjusted automatically to maintain a desired level of accuracy for each step. In this method, the solver performs both RK4 and RK5 for each step, comparing the results to estimate the error-per-step. If the error falls within the desired error tolerance, the solver continues iterating. If the error is too large, the solver tries again with a smaller .
We will now give an example of how to use solve_ivp. Say we want to solve the differential equation
where is a constant. First, we rewrite the equation in the generic form where
The syntax for solving ODEs using solve_ivp is the following:
fun: function that returns the derivative of . Notefun(t,y)must have two arguments: the first argument is the independent variablet, the second argument is the dependent variableyt_span: range[t0,tf]where is the initial time and is the final timey0: initial condition. Notey0must be a list or array. If we are solving for just one function, we would write[y0]as the initial condition.
What exactly does solve_ivp return? Let's check by evaluating its type:
This object named result organizes our numerical solution. (To be more precise, it is an instance of a class.) We can call the information contained therein with the following syntax:
result.treturns a list of values between andresult.yreturns a list of values at those values. In general, we need to index which function we want to return, so we writeresult.y[0]to get the first function. If we were solving for two functions, they would beresult.y[0]andresult.y[1].
Second-order ODEs
solve_ivp can be used to solve higher-order IVPs. If we have an ODE of order , we simply need to rewrite it as a system of coupled first-order ODEs.
For instance, imagine we want to solve the following second-order ODE
We can define a new function to rewrite the equations in the following form:
With this in mind, we can rewrite the coupled differential equation to the form
where
and $$ f(t,y) = \left(\begin{array}{cc} v(t) \
v(t) - x(t) \end{array}\right) , . $$
The following example shows how to solve this system from to , with initial conditions
Note the following:
f(t,y)takes as input a list or array fory, and it returns a list of the same dimensions asy. Note again the ordering of the argumentst,yis important.t_spanisThe initial condition
y0is written as a list of values for each of the functions we are solving for.
As you can see, this look a little chunky. This is because solve_ivp only returns certain values. If you want solve_ivp to return more values, you have to specify this ahead of time using the t_eval argument. Now the plot is much nicer.
Note that including more points in t_eval does not change the accuracy of the calculation. It is simply a matter of how many points are reported by the solver. Typically, many more values are evaluated when the algorithm is run, as needed to achieve the level of accuracy, but these are hidden.
Before we leave this example, it is useful to note that we can write in a matrix form to make the calculation more compact. The function
can be rewritten as a matrix vector multiplication
With this, we can simplify the implementation of our ODE solution.
Exercise 4
We want to solve a classical harmonic oscillator.
Without loss of generality, we can set for convenience and we have
Your tasks are:
Solve the IVP using
solve_ivp, with initial condition and . And plot your results.Calculate the period. Here you can be creative for finding the period.
Compare the numerical solution with the analytical one.
Example: more coupled differential equations
As a last example for Runge-Kutta, we want to solve the coupled differential equation. Let's imagine two frictionless point masses, connected by a spring which is relax at distance . This system can be described by the following differential equation:
We write these two second order differential equations as 4 first order differential equations by defining and :
As you can see, this equation is an inhomogeneous system.
So we get for and :
and $$ f(t,y) = \left(\begin{array}{cccc} 0 & 0 & 1 & 0 \ 0 & 0 & 0 & 1 \
\omega^2 & \omega^2 & 0 & 0 \ \omega^2 & -\omega^2 & 0 & 0 \ \end{array} \right) y(t)
\omega^2 \left(\right) $$
We solve the couple differential equations for , , , , and .
Exercise 5 (Optional)
With this exercise, we put everything together, what we have learned so far. We imagine two frictionless point masses, which are connected by a spring. In contrast to previous example, the left point mass is also connected to a solid wall. The system is described by the following differential equation:
Solve the these equations for , , , , , , and .