Computational Methods for Nonlinear Systems

Physics 7682 - Fall 2014


Instructor: Chris Myers

Mondays & Fridays 1:30-3:30, Rockefeller B3 (directions)

http://www.physics.cornell.edu/~myers/teaching/ComputationalMethods/

Pendulum Exercises

Background

In this exercise we will explore the dynamics of the simple pendulum. The dynamics of a pendulum is described by an ordinary differntial equation. Solving differential equations on the computer is one of the most common scientific tasks. To solve these equations you approximate the continuous-time evolution with a discrete time step. A good algorithm will be accurate, stable, and produce high fidelity results. We will use the following techniques

Learning Goals

Science: You will learn about different algorithms for solving ordinary differential equations in the context of a simple nonlinear dynamical system.

Computation: You will learn to manipulate functions as objects -- passing them as arguments to an ODE integrator. You will learn how to run an interactive python session where one produces and analyzes graphs.

Procedure

A forward Euler Integrator for the simple pendulum

  1. Create a new directory
  2. Download the following files:
  3. Read the description of integration strategies: Integration Strategies
  4. Rename PendulumODEHints.py as PendulumODE.py, and open it in a text editor (for example, kate, gedit, or emacs) or load it into the ipython dashboard (started with ipython notebook --pylab inline) You can leave out the inline flag if you do not want the graphs to appear in the notebook, but rather in a separate window. In this file you will notice Python code that has already been written, but it mostly consists of hints to help you flesh out the code, i.e., comments (lines that begin with #) and documentation strings (material enclosed in triple quotes """ that document what each module, class, and function is about and can be queried with the Python help() function). You will also notice the keyword pass embedded within each function body: pass is a legal Python expression that means "do nothing". The first thing you will do when you start to define a function is to remove the pass statement and replace it with new code.
  5. If you are using the terminal interface than open a terminal window, move to the correct directory and start ipython with the following command ipython -pylab, otherwise click on the file in the ipython dashboard. The "flag" pylab automatically imports the functions from the pylab module into the main namespace, and sets up the graphics software for an interactive session.
  6. Test that pylab has correctly loaded. If you are in a terminal, or if you started the notebook without the inline flag then:
    • Type figure(1) and then plot([0,1,2,1,2]). You should see a graph appearing.
    • Next try plot([0.1,0.2,0.3,0.4,4],[0,1,2,1,2]). Another line should appear on your plot.
    • Start a second figure by typing figure(2). Fill it with a graph by typing another plot command. Have fun.
  7. If you have used the inline flag, then instead type the two plot commands in one cell. Hit shift-enter to run them. You should see both lines appearing on the graph.
  8. Edit the PendulumODE.py file, creating the function EulerIntegratePendulum (using natural units).
  9. If you are in a terminal, type %run PendulumODE.py to load in the function you just defined. If you are in a notebook, just hit "shift-enter" on the cell.
  10. Test by exploring small amplitude oscillations through the following procedure
    • Generate some data by typing testtrajectory=EulerIntegratePendulum(InitialTheta=0.1,InitialOmega=0,InitialTime=0,FinalTime=16*pi,dt=4*pi/1000).
    • Plot a time series by typing figure(3) then plot(testtrajectory[0],testtrajectory[1]). Plot angular frequency on this same graph with plot(testtrajectory[0],testtrajectory[2]). Go to the pylab tutorial to learn how to label figures. Note that you do not have to type the show() commands that are listed in the tutorial -- this is because we are in "interactive mode". Label the axes (using commands xlabel and ylabel), the plot (using title) and the two curves (using legend).
    • Plot the trajectory in phase space by typing figure(4) then plot(testtrajectory[1],testtrajectory[2]). Label the axes.
  11. Test how your algorithm responds to making the stepsize larger by typing testtrajectory2=EulerIntegratePendulum(InitialTheta=0.1,InitialOmega=0,InitialTime=0,FinalTime=16*pi,dt=4*pi/100). Plot the trajectories on the same graphs. IE. type figure(3) then plot(testtrajectory2[0],testtrajectory2[1]).
    • How does this compare? Play a bit.
  12. Complete the PendulumEnergy function.
  13. If you are in a terminal type %run PendulumODE.py to load this function. In a notebook instead hit shift-enter.
  14. You can use this function to get how the energy evolves by typing elist=map(PendulumEnergy,testtrajectory[1],testtrajectory[2]) and elist2=map(PendulumEnergy,testtrajectory2[1],testtrajectory2[2]). [Alternatively, you can forgo the map command if you pass in array objects. For example, elist=PendulumEnergy(array(testtrajectory[1]),array(testtrajectory[2])). It turns out that the array handling routines are faster than the mapping routines, so this is actually the prefered approach.] How does this energy behave when your timestep is large? Plot them on another graph (say figure 5). This energy non-conservation is what motivates using a symplectic algorithm.
  15. Play with your integrator. Can you, for example, produce a phase plane portrait where you plot a number of trajectories with different initial conditions?

A Symplectic Integrator for the simple pendulum

  1. Implement the symplectic Euler method (see Integration Strategies ) -- the routine SymplecticIntegratePendulum should only differ from EulerIntegratePendulum in a couple lines.
  2. Compare with your forward Euler integrator. In particular, what happens to the energy over time when the timestep is large?

A backward Euler Integrator for the simple pendulum

  1. Implement the backward Euler algorithm (see Integration Strategies ). For this you will have to solve a transcendental equation at each step. We will use the scipy.optimize.fsolve function to solve this equation.
    • For more information of fsolve, have a quick look at NanoPy 5
    • To save writing, in the PendulumODE.py file we define fsolve=scipy.optimize.fsolve which makes a function in the main namespace which is the same as scipy.optimize.fsolve.
    • The equations that we need to solve to find the new theta and omega depend on the old parameters and the timestep. We need a mechanism to set these when we call fsolve. There are several strategies for this -- the one we will use is to take advantage of the third argument to fsolve. As examples of how to do this, see the four findsqrt routines w in the Hints file. These are examples of using fsolve to calculate square roots (of course the built in sqrt command is better).
      • Of particular note, the first routine, findsqrt uses the following construction: fsolve(sqrtfun,estimate,(num)). What this does is it repeatedly calls sqrtfun(guess,num) until sqrtfun(guess,num)=0. An even more sophisticated example of this is findnthroot, which has a line fsolve(nthrootfun,estimate,(num,n)). This repeatedly calls nthrootfun(guess,num,n) until nthrootfun(guess,num,n)=0.
  2. Use whichever paradigm you prefer. The hints file assumes you are using the paradigm of the function findsqrt. In this approach we get the new theta and omega at each time step by performing a function call that looks something like newthetaomegalist=fsolve(BackwardPendulumFunction,(oldtheta,oldomega),((oldtheta,oldomega),dt)). The function BackwardPendulumFunction is described in the hints file. If using this approach, complete that function, and complete BackwardIntegratePendulum.
  3. How does the backward Euler integrator compare to the other two algorithms? Note, this nicely demonstrates that implicity methods (such as the backward Euler) are more stable. [Things do not tend to run off to infinity.] On the other hand, the decay in the energy that you see is non-physical.

A general Euler Integrator

We now want to explore some variants of the pendulum. We could write more special-purpose routines for applying these various algorithms to different sets of dynamical equations, but we would then be reproducing a lot of code. Here comes a dramatic leap: we abstract the details of the specific system in a function, and then we can write a function that takes the system-specific function as an argument. The function that we call with will encode the differential equation. The function fsolve that we used in the previous exerciese used this same strategy. We are going to choose the syntax for our function so that it matches that of scipy's built in ODE solver. We will just use the forward Euler algorithm since it is the simplest to implement. When we really want well controlled results we will use odeint.

Our integrator will be called with the following syntax: EulerIntegrate(DerivitiveArrayFunc,InitialValues,times).

  1. Write the routine EulerIntegrate.
    • Test by directly comparing the results to the output of EulerIntegratePendulum. To do this it will be convenient to write a function PendulumDerivArray. When called with a tuple vars=(x,v) and the time t, this function will return the tuple (dx/dt,dv/dt).
    • Your EulerIntegrate function should give identical results to EulerIntegratePendulum. Run with a small number of timesteps to compare explicitly.

Using ODEInt

  1. You probably want to bring odeint into the main namespace. To do this type from scipy.integrate import *. After that you can call odeint instead of scipy.integrate.odeint. In an interactive session this will save you a lot of typing.
  2. Have a quick look at NanoPy 5
  3. If you want to see what is happening under the hood, you can have a look at the reference for the underlying Fortran rountines, odepack.
  4. odeint is called with the same syntax as EulerIntegrate. The t argument functions a little differently though. As with EulerIntegrate, the integrator returns the function evaluated at those times. Unlike EulerIntegrate however, the odeint integrator uses an addaptive step size. At the end it interpolates through this addaptive grid to evaluate the functions at the times you specified. From a design point of view this is probably not ideal. This choice was made because it gives a syntax which is similar to what is used by MatLab. [see section on an improved odeint interface for one way to add functionality.]
  5. Verify that odeint correctly gives the dynamics of the pendulum.

The Period of the Pendulum (optional)

Here we will look at two ways to calculate the period of the pendulum. First the elementary way which involves a simple definite integral. Second using a method called event detection. The latter will be used in the walker exercise.

For the first part, recall that because the pendulum is described by an autonomous differential equation (meaning that the right hand side has no explicit time dependance). One can use the chain rule: $\frac{d\omega}{dt}=\frac{d\omega}{d\theta}\frac{d\theta}{dt}=\frac{1}{2}\frac{d(\omega^2)}{d\theta} Since \omega=-\sin(\theta) we get energy conservation This is integrated to get that the period of a pendulum swinging with maximum amplitude s is the elliptic integral. T=4\int_0^s \frac{d\theta}{\sqrt{2 \left(\cos\theta-\cos s\right)}}

  1. Write the function PendulumPeriod which takes s as its argument and returns the period of the pendulum. It should use the scipy.integrate.quad function.
  2. For very small angles, this function should return numbers very close to 2 pi. For angles approaching Pi, the period should become large.
  3. Make a function PlotPeriod which plots the period of a pendulum as a function of angle.

Next we will do the same thing using a more generic approach that will work even when there is no energy conservation. Starting from our initial conditions (with theta>0 and omega=0) we will integrate the differential equation forward in time by a small amount. If theta is still positive, we keep repeating until theta changes sign. We thereby bracket the time at which the pendulum has travelled a quarter period.

To find the exact quarter period, we use the following trick. We change independent variables from time to angle, writing \frac{d\omega}{d\theta}=\frac{\frac{d\omega}{dt}}{\frac{d\theta}{dt}}=-\frac{\sin(\theta)}{\omega} and \frac{dt}{d\theta}=\frac{1}{\omega} then integrating these equations from the point that our last integration ended to the point where theta=0.

  1. The first step is to write a function which when called by vars=(omega,t) and theta returns a tuple (domega/dtheta,dt/dtheta). We will call this function PendulumDOmegaTDTheta.
  2. Next write the function ODEPendulumPeriod, which given the initial angle will return the period. It will use the algorithm described above, and repeated in the hints file.
  3. Compare the results to PendulumPeriod

Animation

There are several techniques for animating the dynamics of a pendulum. One example is shown in PendulumAnimation.py or PendulumAnimation.enthought.py. The former is for users of "visual", while the latter is for users of "Enthought python". Enthought users should start ipython with the "-pylab" flag if they want to interact with the animation.

This program uses the symplectic Euler algorithm to update the animation. Try it out. Play with parameters. What happens if you switch from the symplectic Euler algorithm to the forward Euler algorithm (this just requires switching the order of two lines of code).

Note, design-wise PendulumAnimation.py is essentially a procedural program -- much like you would write in Basic. A more sophisticated example is in the Walker Exercise. Note, there is nothing wrong with a procedural program. There is no need to make something "object oriented" or "functional" just for the sake of using a structured programing paradigm.

An Improved interface to odeint (optional)

One important aspect of the interactive mode of scientific programming is that you want the interface with the functions/classes to be intuitive. The idea is that instead of spending your mental effort trying to remember a bunch of archane syntax, you should be able to focus on the science. Here is what I want from a numerical integrator:

As an example of how one would write such a function, we have included in the hints file the function smartodeint and the function plotfunction which can be used to plot the output. If you want to try your hand at something similar, you might find it useful to make a parametricplotfunction routine which will plot a parametric curve (so parametricplotfunction(sin,cos,[0,6.28]) would plot a circle). As one example of the usefulness of this approach, try getting half the period of the pendulum by typing the following:
pendtheta1,pendomega1=smartodeint(PendulumDerivArray,(2,0),(0,25))
rt=pendtheta1.roots()
rt[:-1]-rt[1:]
You have to agree that this is a lot more satisfactory that the approach we used above.[You would plot the trajectory with plotfunction(pendtheta1)].

Another integrator (optional)

Useful Constructions/Syntax

Science Concepts

Notes

Units in computation

Units are important. For example, errors in units leads to Airline Crashes, Medical Errors, and lost satellites. They also are a problem for computation. Most programing environments (python included) work with pure dimensionless numbers. It is not easy to propegate units around. An obvious strategy is that you pick some standard units which you will measure all quantities in: for example meters, seconds, and kg.

This strategy has some problems. Consider, for example, the pendulum. If we measure time in seconds, then we will need to know g, L, and M before we can write down the differential equation (or at least the relevant combination of these), and we will need to pass this quantity around in our program. We then will have to adjust all of our parameters whenever we change things. We run the risk of findin very small numbers, or very large numbers. [For example if we were dealing with a nano-Pendulum, then the oscillations might only take a million'th of a second.] A more robust approach is to write your program using natural units. In the pendulum example, dimensional analysis says that the only relevant timescale must by sqrt(g/L). It therefore makes sense to use that for our unit of time.

Such crazy units work great for the computer program. Automatically interesting things tend to happen on the scale of natural units. How do you, as a scientist, keep track of what units you are using? The easiest thing is to write a function to which you pass all of the physical parameters. It will spit back what the time unit is in physical quantities.

Some researchers prefer to just include a comment in the file which expresses what the units are. That can work too, but the advantage of making a function that calculates the units is that you can use that routine internally to do conversions.

Strategies for working in an interactive environment

Lambda Functions

By now you should be comfortable with the idea that a function is an object that can be passed from one place to another. For simple functions, the syntax for doing this can become a little awkward. For example, suppose you wanted to integrate the ODE for a decaying exponential. You might type some thing like this:

def oscfunc(x,t):
	return -x
ser1=odeint(oscfunc,1,arange(0,10,0.1))
It turns out that there is a shorthand notation for defining functions. The following code is equivalent to the last one:
oscfunc=lambda x,t:-x
ser1=odeint(oscfunc,1,arange(0,10,0.1))
The keyword "lambda" is used to define a function. The syntax above defines a function of two variables, which returns the negative of the first argument. Where this notation becomes usefull is when you omit the function definition in the first place, and just type
ser1=odeint(lambda x,t:-x,1,arange(0,10,0.1))
The equivalent command for a pendulum would be
pendser=odeint(lambda x,t:(x[1],-sin(x[0])),(1,0),arange(0,10,0.1))
In a program it is probably best to avoid lambda functions. The construction using def is much more readable. On the other hand, in an interactive session the savings on typing can be significant.