Programming for Physics in Python

196
Programming for Physics in Python Ananda Dasgupta Department of Physics, St. Xavier’s College Kolkata Version 0.5

description

by Ananda DasguptaSt. Xavior's College, Kolkata

Transcript of Programming for Physics in Python

Page 1: Programming for Physics in Python

Programming for Physicsin Python

Ananda Dasgupta

Department of Physics,

St. Xavier’s College

Kolkata

Version 0.5

Page 2: Programming for Physics in Python

Contents

1 The basics 1

1.1 Why study programming? . . . . . . . . . . . . . . . . . . . . . . . . . 1

1.2 What is Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

1.3 Why learn Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

1.4 Example program - “Hello world” . . . . . . . . . . . . . . . . . . . . . 3

2 Dynamics on the computer 1

2.1 A falling particle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

2.2 Algorithm for the falling body problem . . . . . . . . . . . . . . . . . . 2

2.3 The program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2.4 Explanation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

2.5 Using the program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.6 Fitting the data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3 Investigating oscillations 1

3.1 The simple harmonic oscillator . . . . . . . . . . . . . . . . . . . . . . . 1

3.2 Roughing up the oscillator . . . . . . . . . . . . . . . . . . . . . . . . . 4

3.3 What’s the envelope? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

1

Page 3: Programming for Physics in Python

CONTENTS 2

3.4 Plotting in phase space . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3.5 Nonlinear oscillations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

3.6 Measuring time periods . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.7 The good old pendulum . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3.8 Forcing the oscillator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

3.9 Forced vibrations without damping and nonlinearity . . . . . . . . . . . 32

3.10 Parametric resonance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4 More Python - improving the dynamics program 1

4.1 The math module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

4.2 Defining your own functions . . . . . . . . . . . . . . . . . . . . . . . . 7

4.3 Asking the user . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

4.4 Handling files in python . . . . . . . . . . . . . . . . . . . . . . . . . . 16

5 Root finding 1

5.1 Warmup - the quadratic equation . . . . . . . . . . . . . . . . . . . . . 1

5.2 Bracketing roots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

5.3 The bisection method . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

5.4 Evaluating polynomials better . . . . . . . . . . . . . . . . . . . . . . . 9

5.5 The regula falsi method . . . . . . . . . . . . . . . . . . . . . . . . . . 12

6 The power of iterations 1

6.1 The rise and fall of fish populations . . . . . . . . . . . . . . . . . . . . 11

6.2 The iterates of the logistic map . . . . . . . . . . . . . . . . . . . . . . 13

6.3 Solving equations by iteration - the Newton-Raphson method . . . . . 22

Page 4: Programming for Physics in Python

CONTENTS 3

7 Reading between the lines - interpolation 1

7.1 Newton interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

7.2 Lagrange interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

7.3 Spline interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

8 Simulating random processes 1

8.1 “Measurement” of π . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

8.2 Radioactive decay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

8.3 The random walk problem . . . . . . . . . . . . . . . . . . . . . . . . . 6

9 Even more python - taking the drag.py program further 1

9.1 Handling files II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

9.2 Improving the input routine . . . . . . . . . . . . . . . . . . . . . . . . 6

9.2.1 The raw input() function . . . . . . . . . . . . . . . . . . . . . . 6

9.2.2 Default and named arguments . . . . . . . . . . . . . . . . . . . 7

9.2.3 String concatanation . . . . . . . . . . . . . . . . . . . . . . . . 9

9.2.4 A few more improvements . . . . . . . . . . . . . . . . . . . . . 10

9.2.5 Excception handling in python . . . . . . . . . . . . . . . . . . . 11

9.2.6 Documentation strings . . . . . . . . . . . . . . . . . . . . . . . 14

9.3 Your own module(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

9.3.1 Storing your modules . . . . . . . . . . . . . . . . . . . . . . . . 16

9.3.2 Testing your modules . . . . . . . . . . . . . . . . . . . . . . . . 18

10 Getting more ambitious - dynamics part II 1

10.1 Projectile motion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Page 5: Programming for Physics in Python

CONTENTS 4

10.2 Coupled oscillations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

10.3 Kepler orbits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

10.3.1 The Einstein correction . . . . . . . . . . . . . . . . . . . . . . . 8

10.4 The Lorentz butterfly . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

11 A touch of class 1

11.1 Barebones classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

12 Raising the accuracy - better algorithms 1

12.0.1 Taylor series and the Euler algorithm . . . . . . . . . . . . . . . 2

12.0.2 The modified Euler alorithm . . . . . . . . . . . . . . . . . . . . 3

12.0.3 The Runge - Kutta methods . . . . . . . . . . . . . . . . . . . . 5

12.0.4 Taking the earths spin into account . . . . . . . . . . . . . . . . 8

Page 6: Programming for Physics in Python

Chapter 1

The basics

1.1 Why study programming?

Let’s get the very first point clear - the reason why we are studying programming, at

least for this book, is to improve our grasp on physics1. There are several ways in which

writing a computer program to simulate physical phenomena may help in this task

• Enables deeper explorations, beyond those possible by analytical methods.

• Allows “experiments” by simulating systems that may not be possible to access in

the lab.

• Takes the tedium out of repeated calculations, allowing you to focus on physical

issues.

• Trains in precision of expression and logical thought.

1If you, dear reader, have a fancy for some other subject, feel free to substitute the name of thatone. I am afraid, though, that my examples tend to be a bit too physics oriented at times.

1

Page 7: Programming for Physics in Python

CHAPTER 1. THE BASICS 2

1.2 What is Python?

Python is a high level interpreted language that is powerful, easily extensible and highly

modularized. You can learn more about python, as well as access a lot of it’s documen-

tation from the website at http://www.python.org.

1.3 Why learn Python?

Advantages

• Easy to learn

• Powerful

• Extensible

• Style is rigid - so it should be easy for you to read someone else’s program (and

even your own!).

• Object oriented as well as structured programming can be carried out with mini-

mal overhead.

• Interpreted - so you can learn commands directly by checking them out on the

interpreter, as well as dispense with the compile, link execute cycle that you have

to go through each time you modify a program.

• Interpreters are freely available for download on the net for a wide variety of

platforms.

Disadvantages

• Uncommon

• Slow

Page 8: Programming for Physics in Python

CHAPTER 1. THE BASICS 3

1.4 Example program - “Hello world”

The “Hello world” program is, by almost universal consensus, the first program that

anyone learns to write in any given language. All it does is print the message “Hello

world” on the screen and quit. In C, the “Hello world” program looks like :

# include <stdio.h>

void main()

{

printf(’Hello world’);

}

To run this program you will have to store this in a file called, maybe, hello.c and run

the C compiler on your machine to get the output file. On a Linux system you will do

something like

$ gcc hello.c -o hello

to get the executable file named hello2 and finally run the executable by issuing the

command

2Note that the $ sign above is meant to stand for the prompt that the operating shell is going toprovide you with. Your prompt will vary, depending on how your system is set up. For example, inour systems, if you log in to the machine zeews as the user gauss, and have moved to the studentdirectory your prompt will show up as

[gauss@zeews student]$

Do not type the $ sign along with the command too.Whenever you are supposed to type in a command at the shell, we will prepend the command with

a $. Similarly, once you start the gnuplot program, you will be faced with the gnuplot prompt

gnuplot>

Again, the prompt for the python shell is > > >.

Page 9: Programming for Physics in Python

CHAPTER 1. THE BASICS 4

$ ./hello

If all goes well, this should leave you with a screen in which the message “Hello world”

has been typed.

Contrast the above with the case of python. The “Hello world” program in python

consists of the one liner

print ‘Hello world’

when stored in a file hello.py the program can be run simply by saying

$ python hello.py

at the command prompt. This is all you have to do to get the “Hello world” message!

This is not the only way in which you can get the job done in python, which has several

ways of doing most things. For example, one thing that you can do is start the python

interpreter by typing python at the command prompt and the system will respond

with something like

Python 2.3.2 (#1, Oct 4 2003, 13:53:24)

[GCC 2.96 20000731 (Red Hat Linux 7.1 2.96-81)] on linux2

Type "help", "copyright", "credits" or "license" for more information.

> > >

where the >>> sign is the python interpreter’s prompt that’s asking you for a com-

mand. Type

> > > print ‘Hello world’

at the interpreter prompt and it will answer back with the message.

Yet another way would be to add the line

Page 10: Programming for Physics in Python

CHAPTER 1. THE BASICS 5

#! /usr/local/bin/python

to the beginning of your file hello.py, make it executable by issuing the command

$ chmod +x hello.py

and run the file hello.py directly by typing

$ ./hello.py

at the command prompt.

Page 11: Programming for Physics in Python

Chapter 2

Dynamics on the computer

In this chapter we will start to write our first practical physics programs using python.

Instead of spending a lot of time teaching you about the language constructs while

writing programs that add the first ten integers, I have decided to throw full working

physics programs at you. The idea is that by seeing how these programs work, you will

be able to learn enough to write your own.

2.1 A falling particle

One of the first slightly non-trivial problems that you may have solved in dynamics is

that of a freely falling particle under a resistive drag. Here, we assume that the drag

force is proportional to the velocity, so Newton’s second law yields

mdv

dt= mg − kv (2.1)

For a particle falling from rest, the solution is

v =mg

k

(1− e−

ktm

)(2.2)

1

Page 12: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 2

Another integration tells us that the displacement is given by

x =mg

k

(t− m

k

[1− e−

ktm

])(2.3)

In the next section we will see how to solve this problem on a computer. By choosing

a problem for which the exact solution is known, we will be able to check our work - so

that we can tackle other cases where we don’t have an analytical solution handy with

a degree of confidence.

2.2 Algorithm for the falling body problem

Before we can write our python program to solve any problem, what we need is an

algorithm. Loosely speaking, an algorithm is essentially a sequence of steps that lead

us to the answer. We will have more occasion to deal with the finer points of algorithms

later, but this will suffice for the moment.

What we have to do at present is find a way for solving (2.1) which is a differential

equation. So, our job is is to devise an algorithm for solving ODEs. There are a huge

number of such algorithms and we will meet a few of them further down the line. For

now, we will stick to the most obvious one - one that needs no more than class VIII

background to comprehend.

Acceleration is the rate at which velocity changes with time. So, the change in velocity

over a given time interval is nothing but the acceleration multiplied by the duration

of that interval. Adding this to the velocity at the beginning of an interval allows us

to find what the velocity becomes at the end of an interval. Of course, the only thing

that’s wrong with this is that it works only for uniform acceleration. If the acceleration

is variable (and in almost all interesting cases it is), then which value of the acceleration

do you use? The most direct answer to this question is, just use the acceleration at the

beginning of the interval. If you choose a small enough interval of time, the variation

in acceleration will not make a huge difference. Note that unless you are dealing with

the uniform acceleration case, this is of necessity an approximate process. The “better”

Page 13: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 3

algorithms that we will describe later will allow us to improve on the accuracy - but,

as we will see, even this simple minded method works quite well. A similar procedure

also allows us to update the value of the position in each time interval. This, however,

will only give us the value of the position and velocity just a little while after the initial

time. To get the velocity a finite amount of time later, we have to keep on repeating

this procedure as long as we need to. This is the step where the computer becomes

really handy! Carrying out one or two or even twenty such steps may be within your

abilities, especially if you have an electronic calculator at hand. But carrying out the

hundreds of steps that may be necessary to get to the final time without getting bored,

making mistakes and without taking forever is where the computer really is without

peer! As we will soon see, it is easy to make the computer repeat the same (or similar)

steps over and over again - this is what we call looping. Of course, we will also need to

know when to stop repeating so that we are not caught in an infinite loop.

Since the computer does only what it is explicitly asked to and does not make assump-

tions about what you want it to do, there are a lot of additional bookkeeping steps that

you must execute first before the process outlined in the last paragraph can become

an actual working program. You have to tell the computer what value m, g and k has,

what the initial values of position and velocity are, what time interval to use as well as

when to stop. A more complete description of the algorithm would be :

• Set the parameters m, g and k.

• Set the initial and final times ti and tf .

• Set the size of the time interval δt or set the number N of steps to be carried out

and calculate δt from δt = (tf − ti) /N .

• Set the initial values of x and v at t = ti.

• Set t = ti.

• While t < tf do the following

– Calculate acceleration from a = g − kv/m.

Page 14: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 4

– Calculate the change in velocity δv = a δt.

– Calculate new velocity vnew using vnew = v + δv.

– Reset v to vnew.

– Calculate δx = v δt.

– Calculate the new position xnew = x + δx.

– Reset x to xnew.

– Update the time to t + δt.

– Print t, v and x.

• Quit

2.3 The program

One of the beauties of python is that it makes translating a well formulated algorithm

into a program almost trivial. The code written down next should make this obvious.

#!/usr/bin/python

# set parameters

m=1.0

g=9.81

k=1.0

#set initial conditions

ti=0.0

tf=10.0

deltat=0.001

t=ti

x=0.0

Page 15: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 5

v=0.0

# the actual calculation

while (t<tf):

# calculate acceleration

a = g - k*v/m

# update velocity, position and time

v = v + a*deltat # or v += a*deltat

x = x + v*deltat

t = t + deltat

# print results

print t,v,x

print ’Bye!’

Store the above program in a file called drag.py . Make it executable by issuing the

command

$ chmod +x drag.py

and run the file directly by typing

$ ./drag.py

at the command prompt. Alternatively, you can use

$ python drag.py

or, if you are using IDLE, you can choose the Run Module command from the menu

(hit the F5 key). If you carry out any of the above you will see lines after lines of

numbers scrolling off your screen (10,000 of them, to be exact!) until you are left with

Page 16: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 6

the last screenful. It will also take a noticeably long time - not because the computer is

slow in calculating - but because displaying on the screen is a (relatively) slow process.

The last line on your screen should tell you what the values of velocity and position are

at t = 10 seconds. Now that you are reasonably sure that the program is working, we

may want to make better use of the data that it spews out. A latter section shows how

to use this program better. For now, we will take a deeper look at the program itself.

2.4 Explanation

Since this is our first non-trivial python program let’s take a step by step look at it’s

innards.

The first two lines both start with the “hash” character, # :

#!/usr/local/bin/python

# set parameters

There are a few more lines starting with a hash - each of these lines are comments. The

line does not have to start with a #, as you can see from a latter line

v = v + a*deltat # or v += a*deltat

where the part including and after the # is the comment. The python interpreter com-

pletely ignores any comments. What you write in comments is entirely for convenience

- so that a reader can make head or tail out of your program. Since the reader could

mean yourself after a couple of months reading your own program - do yourself a favor -

write comments that make sense to you and hopefully to the rest of the world! Indeed,

I have been rather sparse with the comments here, you might like to add lines such as

# Here m stands for mass

# g for the acceleration due to gravity

Page 17: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 7

and so on.

The comment on the very first line

#!/usr/bin/python

is slightly different from your usual run of the mill comments. The #! construct is

special - it’s the program’s way of telling the operating system (Linux, in this case)

where to find the interpreter (the line may change depending on the location of the

python interpreter on your system). This allows you to make the program executable

and run it directly1. This line is not essential, of course - if you decide to drop it you

will have to explicitly call the python interpreter to run the program

$ python drag.py

The next few lines that follow the initial comments are

m=1.0

g=9.81

k=1.0

These are examples of statements, commands that the python interpreter can execute.

What these statements do is rather obvious, but this brings us to the very important

notion of variables. Lets take the line

m=1.0

1My computer has the python interpreter in the directory /usr/bin - yours may well differ! If youare in a Linux environment type the command

which python

to find out!

Page 18: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 8

and continue else for import not raise

assert def except from in or return

break del exec global is pass try

class elif finally if lambda print while

Table 2.1: Python Keywords

Here m is a variable. In some senses, it is a close kin of the variables that we use

in algebra - but there are quite a few major differences. Corresponding to the logical

variable m there is also the physical aspect. As far as the interpreter is concerned, the

physical variable m corresponds to a region of the computer’s memory set aside for the

purpose of storing the value to be assigned to the logical variable m.

There are certain restrictions on the names that your variables can have. In python,

variable names can have both letters and numbers (no spaces, though) but it has to

start with a letter. So year2004 is an allowed name, but 2004year is not! The name

can be as long as you want it to be, so you can use it to give meaningful names to your

variables. I mean - m is all right, but wouldn’t mass be better? The underscore sign

is allowed in a variable name, but don’t use it at the beginning (a variable beginning

with one or more characters have a special meaning, as we will see later). Remember

that python is case sensitive - while you are allowed to use both the variable m and

M, remember that they refer to different variables! One final restriction that has to be

obeyed is that your variables names should not match that of a python keyword, one of

twenty-eight words that have special meaning to the python interpreter. The full list is

given in table 2.1.

The = sign stands for an assignment. This is where things begin to differ from algebra

quite considerably! The = is not a sign of equality! So, the statement m=1.0 is not

saying that m equals 1.0, but rather that you are assigning the value 1.0 to the variable

m. So, what the interpreter does when it sees this line is place the value 1.0 in the

memory region set aside for the variable m, thereby erasing any value that was there

before. What’s this big deal between equality and assignment? Well a little later we

meet the line

Page 19: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 9

v = v + a*deltat

This is of course nonsense as an equality (unless a or δt happens to be zero!) - however

it makes perfect sense as an assignment. All that this line does is to take the values

that are stored in memory locations designated v, a and deltat , calculates the quantity

v + a ∗ deltat and finally assigns the result to the memory location designated by v. So

what was v before this statement has been replaced by the value given by a ∗ deltat

added to the old value of v2.

Now that you know that = does not stand for equality, an immediate question should

be - “what does?” In python the symbol for equality happens to be == (that’s two equal

signs, one after the other), just as in C. For example, the statement

a==b

compares the values stored in the variables a and b and compares them. The result is

1 (for true) if they are equal and 0 (for false) if they are not.

a==b

2Warning : Tech talk ahead - ignore if not comprehensible on first reading!The above description, though logically correct, is not an entirely precise description of what goes

on when the python interpreter sees the statement

v = v + a*deltat

Actually what the interpreter really does is take the values of v, a and δt from their designated locations,calculate v + a ∗ δt and store the result in - here’s the catch - an entirely different memory location!It is just careful enough to make sure that henceforth the variable name v actually refers to this newlocation, and not the old one. This is something that happens every time you assign a variable tosomething new - even an entirely different type of quantity. Those of you with some programmingbackground (especially in languages like C) might be worried about the possibility of a memory leakhere - just what happens to the physical region of memory where the previous value of v was stored- does it stay blocked up? Not at all! Python has a very good tracking mechanism (called, ratherappropriately, garbage collection) which automatically checks for memory locations that are no longerin use and hands them back to the pool of available and ready for use memory locations - therebyavoiding nightmarish scenarios (which are, unfortunately, all too plausible, and common, if you areusing C and are not very careful) where a program keeps on blocking memory until the computerrefuses to run on what little is left!

Page 20: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 10

is an example of a conditional statement. Other examples are

a < b

a <= b

a > b

a >= b

etc. In each case, the statement returns a value of 1 if it is true and 0 if it is false.

Just how much space is reserved for one variable? That depends on the system you are

using - but it also depends on the kind of variable you are talking about - it’s type.

The variable m in this example is an example of a floating point variable, (so called

because of the decimal point!) also known as a float for short. The floats are what

we would call real numbers. Python has a huge number of data types - integer, long

integer, complex, string, tuple etc. to name but a few. We will dig more deeply

into this issue later. For the time being note that all the variables that we are using in

this program are floats.

One question that might worry you is - how did the interpreter know that we want the

variable m to be? This is an important issue, since unless it “knew” that we want a

float, it wouldn’t know how much space in the memory to assign for it. In statically

typed languages like C and Java, this problem is solved by demanding that you must

explicitly declare the type of every variable that is used - the m=1.0; must be preceded

by a line float m;or else the gcc compiler3 is going to throw at you such errors as

drag.c: In function ‘main’:

drag.c:5: ‘m’ undeclared (first use in this function)

3If you don’t know this already, the gcc (or GNU C) compiler is the standard open source Ccompiler available by default in Linux.You can also freely download versions for other OS - including,MS windows! The reason why I mention the gcc compiler here in particular is that the precise formthe error message will take depends strongly on the particular compiler you use - although the contentwill be (at least roughly) the same!

Page 21: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 11

Python, on the other hand, is dynamically typed - the interpreter determines what sort

of variable m has to be in runtime, when it sees what value is assigned to it! The value

1.0 assigned to m is an example of a python constant, and it is a constant whose type

is float (that’s rendered obvious by the presence of the decimal point). The 1.0 on the

right hand side of m=1.0 then, tells the interpreter that it is dealing with a float. This

is how the interpreter knows just how much memory space to set aside for the variable

named m (the same goes for all variables that you assign).

Of course, logically, all that the lines

# set parameters

m=1.0

g=9.81

k=1.0

#set initial conditions

ti=0.0

tf=10.0

deltat=0.001

t=ti

x=0.0

v=0.0

do is to set the value of the variables m, g, k, ti, tf , δt as well as the initial values of x

and v. Note that the line

t=ti

is slightly different from the other assignments. The entity on the right hand side is

itself a variable. The meaning of this line is clear, though. When the interpreter sees

this, it reads the contents of the variable ti and stores this in a newly created variable

Page 22: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 12

t. To stress a point that should be obvious here, the type of the variable t is set to be

the same as the type of the variable ti whose value is being assigned to it.

The real work is carried out by the program takes place in the part of the program

while (t<tf):

# calculate acceleration

a = g - k*v/m

# update velocity, position and time

v = v + a*deltat # or v += a*deltat

x = x + v*deltat

t = t + deltat

# print results

print t,v,x

This set of lines is really a single statement - our first compound statement. All

compound statements in python are of the form

HEADER :

STATEMENT 1

STATEMENT 2

...

STATEMENT n

The first, header, line of a compound statement ends with a : - it signals to the

interpreter that the next line is the beginning of a block of statements that make up

it’s body. Note the indentation - all the statements within the body are equally indented

- the first line which is not indented is outside the block. Those of you have programmed

before will do well to contrast this to C - there a block of statements has to be enclosed

in curly brackets. Note that in C programmers may or may not chose to indent the

blocks. Whitespace characters such as tabs, spaces, newlines have no significance in C,

Page 23: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 13

so while a programmer may use them to his advantage in making the program more

readable - someone else may write the same code in a perfectly incomprehensible jumble.

Moreover, what constitutes readability is slightly subjective, so different programmers

may write their codes in different styles - adding to the confusion. On the other hand,

let me stress that the indentation in python is not a matter of style - it is part of the

syntax. So, everyone is forced to write their code in the same way - leading, hopefully,

to more readable code!

The block in a compound statement can have as many lines as you want (of course,

the computer’s memory limits this to some astronomically big value - but don’t worry

about that now!). However, there is a lower bound - you have to have at least one. So,

once you have a colon ending one line - the next one has to be one that is indented (or

else you get a syntax error). This may prove to be something of a bother - especially

if you are in the middle of developing a program and want to write in a header line

simply as a reminder to yourself that a certain piece of code has to be written later.

The solution is to use the pass keyword in the block as a single line - this is simply

your way to tell the interpreter - do nothing!

Of course, it is possible that one of the statements within the body of a statement

is a compound statement itself. If that happens, what we get is a nested compound

statement that looks like

HEADER 1:

STATEMENT 1

...

HEADER 2:

STATEMENT a

STATEMENT b

...

STATEMENT n

Note the indentation! The header of the inner compound statement is a statement

belonging to the outer block and so it is indented by the same amount. The next

Page 24: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 14

set of lines (the inner block) are indented by a bigger amount The inner block ends

the moment the indentation returns to the level of the outer one. One glance and the

indentation allows you to see the logical structure of the blocks - one day you will thank

python for it!

If you use a standard text editor to write your python program, you will have to take

care of the indentation yourself. Inconsistent indentation will lead to syntax errors - so

take care! If you use a python aware editor, like the one that comes with IDLE, the

job is easier. Indeed, the moment you hit the <Enter> key after typing the ’:’ , the

next line will be automatically indented - and so will the succeeding lines, until you hit

<backspace> to change matters.

The while is our first example of a python keyword. What the keyword while means

is easy to understand - it asks the interpreter to keep on performing the block of

statements following this line repeatedly as long as the conditional statement t<tf is

satisfied (i.e. as long as it returns the value 1). This is one example of a loop in python

- as we will see later, python allows you to write several other kinds of loop. The lines to

be executed as long as the condition stays correct are the indented ones just following

the colon. Actually the last line in the program is not needed at all - I just wanted to

show you that the block ends the moment the indentation returns to the outer level!

Let’s now see what happens inside our loop. Leaving the comments aside the very first

line in the loop is

a = g - k*v/m

This is an assignment statement - but of a different type than the ones we have seen

before. The difference lies in the right hand side - which is a combination of variables

(g, k, v and m) and operators (−, ∗, /). What the operators stand for is obvious. What

python does when it sees this line is first evaluate the expression on the right, taking the

current values stored in the variables, and assign the result to a new variable a. The first

time you enter the loop, the values in the various variables are g = 9.81, k = 1.0, m = 1.0

Page 25: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 15

and v = 0. So the value that the variable a gets is

a = 9.81− 1.0× 0/1.0 = 9.81

As you will soon see, by the next time the loop executes, the variable v has changes to

v = 0.00981. So this time a will end up with a value of

a = 9.81− 1.0× .00981/1.0 = 9.80019

and so on.

One important point should be mentioned here. If we had forgotten to write the line

v=0

earlier in the program (or for that matter any of the assignment statements before) the

interpreter would have been stumped when it gets to this line, since it cannot retrieve

the value of the variable v. Indeed, without the prior assignment statement, there is no

variable v - so you just can’t go ahead and ask python to do a calculation that needs its

value! In such an event, the interpreter would rather testily throw at you the message

NameError : variable ’v’ is not defined

If you had mistyped the line so that it read

a = g - k*v/M

you would get the error message

NameError : variable ’M’ is not defined

Page 26: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 16

Can you see why?

In fact, if you had not included the assignment statements prior to the beginning of the

loop, the very first error would have occurred in the while statement itself - can you

see why?

The next few lines update the values of v, x and t. Note that statements like

v = v + a*deltat

are “destructive”. Although the current value of v is used in evaluating the value of the

expression on the right, once executed the value of v is replaced by the result of the

calculation, and you lose the old value of v. The same of course goes for the lines

x = x + v*deltat

t = t + deltat

This is not a problem for the current program, but there may be situations where the

old value of v may be needed. For example, a better algorithm for updating the position

may be one in which you use the average of the old and the new values of the velocity

to calculate the displacement in a particular interval. For this we need to change the

lines for updating the velocity and the position to

vn = v + a*deltat

x = x + (v+vn)/2*deltat

v=vn

This is more or less self-explanatory. Do pay attention, however, to the last statement.

If you forget this the velocity won’t get updated, and you will have a program with a

semantic error , one that conforms to all the laws of python, runs without a hitch -

and gives a completely wrong answer!

As noted in the comment, there is an alternative way of writing

Page 27: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 17

v = v + a*deltat

and this is

v += a*deltat

In general, v = v + x is completely equivalent to v += x . However, this syntax is

used only in the more recent versions of python - so use this with care.

Forget to update the time t and you will have an infinite loop in your hands. In this case

the variable t forever retains its initial value of 0 and hence the conditional governing

the loop stays true. So the loop should go on and on! In actual operation, the loop

does not really go on for ever - the interpreter gives up after a suitably long time - but

we do not really want to wait that long, do we?

The final line in the loop is what rewards us for our pains so far - in this

print t,v,x

the interpreter prints out (to the screen by default, unless redirected - see below!) the

values currently stored in the variables t, v and x. print is another example of a python

keyword. Note that since the variables being printed have already been updated once

before this command is met for the first time, this list will not have the initial values of

the variables. A solution to this is to put the print statement at the beginning of the

loop, so that the interpreter first encounters this before the updating take place. This

way, however, you will lose out on the last values (can you see why?)! Find a way to

modify the program so that you manage to include both the initial and final values.

The very last statement that you will face is the one that is not really necessary - the

print ‘Bye!’

Page 28: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 18

The reason that I have included this is, as I have already explained, to show that

the block that is executed as the body of the while loop is ended the moment the

indentation goes back to the previous level. Apart from that, the line also serves to

introduce another important python type that we have not met so far. The constant

"Bye!" is an example of a python string, which is any sequence of letters, numbers,

whitespaces etc. (with certain restrictions that will be spelt out later) enclosed in

quotes. While printing the value of the string, the interpreter removes the quotes - so

all you will see in the last line is

Bye!

2.5 Using the program

The trouble with our program as it stands is that it does not yield very useful data - or

rather, that it does not yield data in a very usable form. Run it, and all you see is lots

and lots of numbers scrolling off the screen until you get to the last screenful. so, if you

are interested in what happens at (or a little before) t = tf you are in luck! However,

what you may like to know is how the velocity and position varies starting right from

the initial time. Your program is calculating (and showing) all of these - but how can

you get hold of these results?

If you are using IDLE, you can manage to get hold of all the data. You can simply

scroll up the window in which idle writes all the data. If your idea of an interesting day

is staring at screen after screen full of numbers, congratulations! A more likely scenario

is that you would like to do something more useful with these numbers, now that you

have got hold of them.

To be useful, your data should be sitting in a file on your hard disk, rather than staring

at you from the screen. We will later see how to improve the program so that it

automatically stores the results in a file, rather than show it to the screen. For the time

being, let’s make use of a rather simple device that the Linux operating system gives

us, just change the command calling the interpreter to

Page 29: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 19

$ python drag.py > drag.out

Here ‘>’ is the Linux (rather, Unix) redirection symbol - it redirects the output from

the screen (the default) to the file named after the ’>’. In this case the OS will create a

new file called drag.out and write your output to it. Careful, though - if your directory

already has a file called drag.out, you will land up overwriting it!

Now you have a rather huge file called drag.out with lots and lots of numbers. How can

you make sense of all these data? Well, one way is to see a plot of the results. For this

we make use of the friendly neighborhood plotter program, gnuplot.

Open a X-terminal (if it isn’t open already!). Issue the command

$ gnuplot

at the command prompt and you will see a lot of lines on the terminal like

G N U P L O T

Linux version 3.7

patchlevel 1

last modified Fri Oct 22 18:00:00 BST 1999

... ( a lot of lines here)

Terminal type set to ’x11’

gnuplot>

All that concerns you right now is the final line - the gnuplot>. This is the gnuplot

prompt, where you are to enter your commands. Also, pay attention to the line im-

mediately before the prompt, it tells you that the terminal type has been set to ‘x11’

- which is the default graphical terminal in Linux. What this means is that gnuplot

sends it’s plots directly to the screen, so that you can see them. That’s fine for now,

but you may want to change this later. For example, to produce a printable version of

your plot, you may want the output terminal to be set to ‘postscript’ - but more on

that later.

For the time being, issue the command

Page 30: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 20

Figure 2.1: Screenshots of the plots from drag.out. On the left, t vs. v - on the right, tvs. x.

gnuplot> plot ’drag.out’

at the gnuplot prompt (of course, what you type is just plot ’drag.out’ and not the

gnuplot> - that is just the gnuplot prompt!)and a nice graph should spring up in front

of your eyes. Instead you will find a nasty error message saying ‘Bad data on line 10002’

- or something of that sort! No need to panic - it’s just that gnuplot has found the

“Bye!” at the last line and is throwing a tantrum! The simplest way out is to open

the file drag.out in your favorite editor and get rid of the offending last line, and ask

gnuplot once again (since the only reason that the print ’Bye!’ command was there

in your program in the first place is cosmetic, it is now time to seriously consider editing

the program itself to get rid of it) ! This should give you a t − v plot for the falling

body. Experiment by adding either w l or w d to the end of the last command (you

don’t have to retype the command - just use the cursor (arrow) keys to get the history

to repeat!

Now that you have got your t− v graph, take a good look at it. You should be seeing a

graph that rises rapidly at first, and flattens out later. Make sure that you understand

the physics that is causing the graph to be the way it is!

Of course, what is being plotted are the calculated values of the velocity versus the

time. What gnuplot does, when asked to plot a data file is to plot the second column

versus the first by default. In our file, the first column is the time and the second is the

Page 31: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 21

velocity so we get the t − v plot. What if we want to plot the position (which is the

third column of our file) versus the time? Just tell gnuplot to

gnuplot> plot ’drag.out’ using 1:3

and you will get the t−x plot. Can you understand it’s shape (parabolic to begin with,

nearly straight afterwards) in simple physical terms?

Since we have used a rather simple-minded algorithm to calculate the velocity and the

displacement in our program, you may feel a bit queasy about accepting the results it

spews out! In this case, we have an advantage - we know the exact results! So, it is

very easy to check how good (or bad) our results are. This will stand us in very good

stead later on, when we move over to solving problems that we can not really handle

analytically - so that knowing whether our numerical algorithm is any good at all is

very very important. To get a rough idea of how good our results are, lets see how the

graph we have got compares with the exact result. With gnuplot, this is nearly trivial

- all you have to do is issue the command

gnuplot> plot ’drag.py’, 9.81*(1-exp(-x))

where what follows after the ‘comma’ is just the exact solution for the velocity! In fact,

if you want to plot several graphs, together, all you have to do is give them one after

the other, separated by commas. Note the green line4 that goes through all the red

points - you can hardly make out the difference between the values of the velocity that

your program has calculated and the exact answer!

2.6 Fitting the data

In the last section, we have seen how well the data we have obtained from our program

fits the theoretical answer. However, in this case, we had the advantage that we already

4The line is green because the second linestyle that gnuplot has has the colour green - which it uses,naturally, for plotting the second set of data.

Page 32: Programming for Physics in Python

CHAPTER 2. DYNAMICS ON THE COMPUTER 22

knew the exact answer. We will usually not be in such a fortunate position always. In

this section we will briefly discuss how to analyse the result that our program throws

at us and try to guess at the underlying functional form. Let mke hasten to point out,

though, that data analysis is something of an art - and a pretty advanced one at that.

We will be just scratching at the surface of this vast topic here - we will have plenty of

occasion to hone our skills as the book progresses.

One look at the plot of 2.1a should convince you that this is a classic case of an

exponential approach to a steady value - an initially steeply rising curve that gets

flatter and flatter as we go along.

Page 33: Programming for Physics in Python

Chapter 3

Investigating oscillations

Now that your program has told you how a body moves under gravity when a drag is

present, you can try becoming more ambitious and try it out for other force laws. The

beauty of our simple program is that it makes modifications such as this very easy. All

you have to do is change the line calculating the acceleration - and you are ready to go!

3.1 The simple harmonic oscillator

As an example, let us try out another very simple problem to which we know the exact

answer - the simple harmonic oscillator. For this, the force law is

F = −kx

of course, the quantity k in this equation is the force constant, which is quite different

in character from the k that we had in our earlier force law, where it stood for the drag

coefficient. However, all we need to do to see what our algorithm says for the motion

of a body undergoing an SHM is to change the line

a = g - k*v/m

1

Page 34: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 2

to

a = - k*x/m

Of course, you may want to change the name of the program, now that it no longer

talks of the motion of a particle falling under drag. It is easy to do this from IDLE -

once you have changed the program, just use the Save As ... option from the File

menu to name your modified program as SHM.py. Now, all you need to do to see the

way a harmonic oscillator moves is to issue the command

$ python SHM.py > SHM.out

from the command line. If you have gnuplot running in your terminal, you will have to

start another one. There is a shortcut, though. You can treat the gnuplot command

line itself for this purpose if you just add a ! sign to the beginning of your command,

so that things look like

gnuplot> !python SHM.py > SHM.out

- when gnuplot sees the ! mark at the beginning of a command, it passes the command

to the shell directly, so that the effect is the same as far as you are concerned. Use

gnuplot to plot the data in the SHM.out command, (use u 1:3 to plot the t−x graph)

and you may be in for a surprise - instead of a nice sinusoidal graph springing up in

front of you, all you see is a horizontal straight line! Plot the t− v graph and what you

get is the same thing again! So, just what went wrong?

Nothing, really! All that has happened is that although you have changed the force

law, you have not changed anything else, in particular, you have not modified the initial

conditions. They are stuck at the values x = 0, v = 0 at t = 0, which were appropriate

for the motion of a body that has been dropped from rest. If you were to start your

SHM with the same initial conditions - the oscillator at rest at the equilibrium position,

it will stay put at the same point for ever! This, indeed is what your program is telling

Page 35: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 3

Figure 3.1: Screenshots of the plots from SHM.out. On the left, t vs. v - on the right,the phase space plot v vs. x.

you, too. So, there is nothing wrong with your program - only, you may not really

have wanted it to spew out the trivial result that it did! The remedy is clear - all

you have to do is to change, for example, the initial value of x from 0.0 to, say, 10.0.

Run the program again, and now plot the t − x and t − v graphs and you should see

just the sinusoids that you were expecting! Note that no where in our program we

have mentioned sines and cosines - but somehow even our simple algorithm reproduces

them rather accurately! From the graph, you should be able to find the period to be

6.28 - which is just what the theory of oscillations tell us it will be! To see more than

one complete period so that you may be able to see the periodic nature of the motion

completely, you may change the value of tf from 10.0 to, maybe, 50.0 and run the

program again. You may play around with the initial values of x and v to investigate

the effects of initial conditions on the subsequent motion.

One more important plot that you can ask gnuplot to do for you in this case is

gnuplot> plot ’SHM.out’ u 2:3

What this does is to plot the data from the second column of the file SHO.dat along

the X axis and the third column along the Y axis - so that you end up with a v vs. x

plot.The result is a simple ellipse - after all, energy conservation tells us that the energy

E =1

2mv2 +

1

2kx2

Page 36: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 4

is a constant, which is precisely the equation of an ellipse! This plot is called the phase-

plot - you will have occasion to get to know this better in your advanced mechanics

course.

What will happen if you were to superpose a constant force on top of a SHM? To

answer this, we can appeal to a modified version of our program Just change the line

calculating the acceleration to

a=(-k*x+F)/m

and add an initial assignment statement for F , such as

F=1.0

near the beginning of the program (what will happen if you forget to do this?). Save this

in a file ’forceSHM.py’ - run it while redirecting the output to the file ’forceSHM.out’.

Plotting the output will show you that the displacement is almost the same as before,

but the equilibrium position now is no longer at the origin, but is displaced towards the

added force (i.e. towards positive X). A bit of thought should make the physics clear -

the equation of motion

md2x

dt2= −kx + F = −k

(x− F

k

)shows that the quantity x− F

ksatisfies the standard SHM equation. Hence oscillations

do occur as before, but about the new equilibrium position, given by x = Fk.

3.2 Roughing up the oscillator

A very simple modification will allow you to see the effect of damping on a simple

harmonic oscillator. Obviously, all you need to do is to change the rule for calculating

the acceleration to

Page 37: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 5

a = (-k*x-gamma*v)/m

and remember to add a line like

gamma = 0.1

in the beginning section (anywhere before the loop starts will do). Save this file as

dampedSHM.py, run python ... all right! by now you know the routine. However do look

at the output carefully, and in particular, try to understand the nature of the phase

plot (the x− v plot).

The final SHM related case that we are going to treat is that of a SHM occurring on a

rough tabletop - i.e. under the effect of kinetic friction. What distinguishes this case

from the last example is that here the damping force is constant at µmg, rather than

proportional to the velocity. At first sight we may think that all that will happen is

that the mean position will get displaced - so that the SHM will continue undamped

as before, albeit with a different amplitude. The conclusion is, of course, completely

wrong! Friction is dissipative - so it should take energy away from the system, eventually

making it stop. What is wrong with the argument above is that we were too taken in

by the idea that kinetic friction is constant - actually, it is only the magnitude that is

fixed, it’s direction keeps on switching to always be directed against the velocity! So,

the net force on our body on the rough tabletop is given by

F =

{−kx− µmg if v > 0

−kx + µmg if v < 0

In order to calculate the acceleration, what our program needs to do is to take a decision

based on the sign of v. We have already seen an example of the program taking a

decision, whether to carry out the loop one more time or not was based on whether t is

still below tf or not. In the current case, we need the rather obvious python keyword

if ... else, and the usage is so much like its English counterpart that I am just quoting

the relevant lines of code without explanation :

Page 38: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 6

Figure 3.2: Screenshots of the plots from the two damped oscillators. Top row, on theleft, t vs. x - note the familiar exponential damping. On the right, the phase space plotv vs. x - note how the “ellipse” spirals in as the particle loses energy! The lower lineplots the same quantities for the SHM under kinetic friction - note in particular thelinear decay envelope, as well as the difference between the two phase space spirals!

Page 39: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 7

if (v>0):

a = (-k*x-mu*m*g)/m

else :

a = (-k*x+mu*m*g)/m

Note the indentation (and don’t forget the ‘:’) - if the condition v > 0 is true, the set

of indented lines after this will be executed (in this example, there is only one such

line, a = (−kx − µmg)/m - but in other programs there may be more.); if it is false,

the indented block following the else : will be executed. After this, the interpreter

merrily continues to execute the rest of the program. Of course, before you save and

run this program (naming it, maybe, roughSHM.py) - you need to add a line assigning

a value to the variable mu.

Once you run the program and plot it’s output, you may be in for a surprise. The

amplitude does decrease with time, but this time the envelope is straight and not

exponential as it was with viscous damping. The output is so simple that it cries out

for a simple explanation - try to find it! Just remember that though the force of friction

is not a constant, it is constant over each swing of the motion!

The more perceptive among you may have noticed that we have not really taken the

physics into account exactly. In particular, the body must stop at each endpoint of

the motion, and the friction at the end point is actually static and not kinetic. So, the

motion stops at one endpoint if the limiting value of static friction exceeds the force

exerted by the spring there. See if you can modify the program to put in this bit of

physics.

3.3 What’s the envelope?

In the last section, we saw two examples of damping applied on harmonic oscillator -

viscous drag and kinetic friction, respectively. As we have already remarked - the decay

is different in nature in the two cases - exponential in the former as opposed to linear in

the latter. This is borne out by the shape of the x− t and v− t plots that we can obtain

Page 40: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 8

from our program, as seen in figure 3.2. However, the more sceptical among you may

question - are the envelopes really of the nature quoted above or are the resemblences

just superficial? To answer this question, we may just as well turn to our trusted friend

the python program and this time ask it to print out the values of t, v and t, x whenever

the corresponding dynamical variables reaches an extreme value. Plotting just these

points will tell us what the envelope is.

The first question, then, is - just how can we be sure that either v or x has reached an

extremum? Let’s answer this question for v - a similar notion works for x. We can keep

tab on the successive values taken by v and ask the program to print out the neccessary

data whenever v reaches an extremum - but there is a simpler way to achieve the same

effect. Taking a cue from calculus, note that the exteremum in v is reached whenever

a, the acceleration reaches zero. Of course, we do notreally expect the a to be exactly

zero - but it does change sign each time the velocity crosses an extremum. thus all

we have to do is compare the new value of a with the old one - if they are of different

signs, we have just crossed an extremum in v. Do note that each time the new value of

acceleration is being calculated we lose the old one - we have to be careful in order to

actually carry out the comparison. the solution of course is to store the acceleration at

the end of each loop in a new variable, called, maybe, aold and compare the new value

of a with aold. The loop in this equation then looks like

while t<tf:

a = (-k*x-gamma*v)/m

v = v + a*deltat

x = x + v*deltat

t = t + t*deltat

if a*aold<0.0:

print t,v

aold = a

.1

1To get a program that returns the extrema of x all you need to do is change the last three lines

Page 41: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 9

(a) (b)

Figure 3.3: The velocity envelope of the damped harmonic oscillator.

Redirecting the output of this program to a file called dSHMenvV.out, and plotting the

data in this file with gnuplot gives us the plot in figure 3.3a. This does suggest an

exponential decay. However, if we try to make the decay envelope clearer by using

the w lp option (with linespoints) of gnuplot we land up with the zigzag line shown in

figure 3.3b. What has gone wrong here is easy to understand - if the w lp option is used

gnuplot plots the successive points in the order in which they occur in the original file,

joining successive pairs of points! In this case, each maximum in velocity is followed by

a minimum (our program, which just looks for the aceleration changing sign, does not

distinguish between these two cases) - hence the zigzag line!

One way out of this problem is to modify the if statement in our program to

if aold>0.0 and a <0.0:

print t,v

which will print out only the maxima (can you see why?). However, that will give us

above to

if v*vold<0.0:print t,x

vold = v

Page 42: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 10

(a) (b)

Figure 3.4: The velocity envelope where the magnitudes of the extrme values of thevelocity are plotted.

only one half of the envelope - a solution that we may not like very much. Of course,

we could re-run the program after changing the if statement above to

if aold<0.0 and a>0.0:

print t,v

which will, of course, give the lower half of the envelope. This does seem to be too

much trouble to take for such an easy task!

Another quick way out is that to note that minima in this case are all negative, while

the maxima are all positive - with the magnitudes decreasing exponentially. If all we

wanted to do is capture this exponential decay, one way to go about it is to plot the

file with

gnuplot> plot ’dSHMenv.out’ u ($1):(abs($2)) w lp

We had met the using option before - the u that you see in the last line is a short cut

way of saying that (gnuplot has this habit of automatically expanding all options that

you give - if there is a unique option statring with what you have given, it will use that.

In this case the only option that starts with u is using, so just typing u will do. Saves a

Page 43: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 11

lot of typing in the long run, at the cost of rather cryptic looking commands - take your

choice!). What you see here is that gnuplot can not only use various columns of data

in your datafile, it can als do manipulations on them. Here ($1) and ($2) are gnuplot’s

way of referring to the data in the first and the sond coumn, respectively. So, in the

above line we are asking gnuplot to plot the absolute value of the data in the second

column against the data in the first column. The result, shown in figure , shows clearly

the exponential decrease in amplitudes. If you are the sceptical kind, may be you will

find figure , generated using

gnuplot> plot ’dSHMenv.out’ u ($1):(log(abs($2))) w lp

more convincing!

I believe you will all agree that there is something unsatisfactory about this solution,

however. We do get to see the exponential nature of the decay this way - but only at

the cost of losing sight of the actual shape of the whole envelope! The last solution that

I will talk about here is perhaps the best way - but it comes at a cost! All good things

do! We have to use an option called every that gnuplot provides us which allows us to

plot a selected part of the data file. Now, every is a very powerful option (just think

of how many different ways in which one may waqnt to plot parts of a datafile!) and

hence is rather complicated to use at first sight. I will not waste time here in describing

all possible ways in which it can be used but confine myself to the job in hand. If you

want to access the full powers of the every option, look up the online help in gnuplot

gnuplot> help plot every

The current job at hand involves plotting out the contents of the file dSHMenv.out so

as to be able to get both halves of the envelope shown by a solid line. Note that the

whole trouble is that this file contains the values of the maxima and minima alternately

- thus causing the zig-zag line in figure 3.3b. So, all we need to do is to figure out how

to get gnuplot to plot out every second line in the datafile. This turns out to be very

simple - all you need to do is

Page 44: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 12

(a) (b)

Figure 3.5: The full velocity envelope .

gnuplot> plot ’dSHMenv.out’ every 2 w lp

which will immediately give you a plot of every second datapoint in your file joined by

a line. This will only give you the lower half of the evnvelope, though (can you see

why?). To get the upper half, we will have to ask gnuplot to plot out every second

point, but starting with the second point in the file! To do this, what we need to do is

gnuplot> plot ’dSHMenv.out’ every 2 w lp," every 2::1 w lp1

- the result is shown in figure 3.5a. Remember that we can ask gnuplot to plot more

than one curve at a single time just by giving it what to plot in each, separated by a

comma. The important shortcut that you see being used here is that if you want to use

the same datafile again, you don’t have to type out its name - using a pair of empty

quotes " suffices. As for the every option, the above is the syntax for plotting every

second point, starting from the second point in the file. Note that gnuplot, like most

things in computers starts counting from 0 - so point number 1 is, actually, the second

point. For details, do look up the help in gnuplot. Finally - what is this ‘1’ doing at

the end of the command? Well, if you had left it out (try it for yourself) you would have

got a green line for the upper half and a red line, as always, for the lower one.That’s

because green is the colour of the second linestyle that gnuplot has - and by default

Page 45: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 13

the second line to be plotted is plotted with the second linestyle! The option ‘1’ at the

end of the w lp option actually forces gnuplot to use the first linestyle (a red line with

red points) for this plot also - making both halves of the envelope look the same. In

figure 3.5b, we have actually thrown in the original v− t graph as well - figure out just

how to do this in gnuplot.

3.4 Plotting in phase space

By now, we have seen several examples of phase space plots. These simple plots carry

a surprising amount of information about the nature of any system. Indeed, quiet a lot

of detailed investigation carried out in modern day cutting-edge mechanics involves the

nature of these plots. In the current section we will take a deeper look into them.

Let us make a start by re-examining the phase space plot of the undamped harmonic

oscillator. We have already seen that the plot should be an ellipse and its size should

scale with√

E. Let us verify this by plotting a series of phase plots for various energies.

In order to do this, all we need to do is essentially add an outer loop to our existing

SHO program - in which the energy will take a sequence of different values. Of course,

since the energy is related to the amlitude A by E = 12kA2, all we need to do is change

the amplitude by a fixed increment on each pass of the loop and allow our dynamics

program to take over. So, a rough and ready version of the program to do this could

be

k = 1.0

m = 1.0

Ai = 1.0

deltaA = 0.4

Af = 8.0

A = Ai

while A < Af:

x = A

Page 46: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 14

v = 0.0

t = 0.0

deltat = 0.01

while t < 6.3:

a = -k*x/m

v = v + deltat*a

x = x + deltat*v

t = t + deltat

print t,v,x

print

A = A + deltaA

Saving the output of this program by redirecting to a file called, say phasePlot.out

and plotting the phase plot using

gnuplot> plot ’phasePlot.out’ u 3:2

leads to the plot in figure 3.6. As expected we see a series of ellipses corresponding to

increasing energies. We have, of course, used a bit of insider knowledge in the program

- the time period of all these oscillations are equal to 2π - which is why we have stopped

the inner loop when t ≮ 6.3. Note the blank print statement - it prints out a blank

line separating the data for each energy. You should appreciate that the indentation

syntax of python makes it easy to understand that this statement, for example is part

of the while A < 8.0 : loop, while being outside the while t < 6.3 : loop.

3.5 Nonlinear oscillations

Of course, we do not have to stop short at the simple harmonic oscillator. We can

just as easily plot the phasespace trajectories of nonlinear oscillators (as well as other

dynamical systems) using a small modification of our program. For example, let us

Page 47: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 15

Figure 3.6: The phaseplots for a simple harmonic oscialltor

consider an additional restoring force λx3 acting on our simple harmonic oscillator, so

that the net force on it is given by −kx − λx3. If you are wondering why we have

not considered a force proportional to x2 and jumped straight to the cubic case - the

simple answer is that the motion would not have been bounded and periodic in the

former case. Figure 3.7a shows the phaseplots for the values k = 1, m = 1, λ = 0.2.

Note that for small amplitudes, the plots look like ellipses as for the SHO, whereas for

larger amplitudes, the difference from the SHO plot become quiet marked. This is only

to be expected - after all, the extra term λx3 is quiet small for small values of x - but

becomes quiet the dominant term for large values of x.

What if the restoring force has the form +kx − λx3? This is called the “double well

force”. to see why, try plotting the potential energy corresponding to this force as a

function of x. It is obvious that now the point x = 0 is still an equilibrium position,

but if we move only a small distance from it, the force that acts on it is ≈ +kx and this

is not a restoring force! So, the point x = 0 is not a stable equilibrium position but is,

rather, a point of unstable equilibrium. It is easy to check that the points x = ±√

are

the actual positions of stable equilibrium in this case. What difference will this make

Page 48: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 16

Figure 3.7: Phase plots for (a) the nonlinear oscillator under the restoring force −kx−λx3 and (b) the “double well” force +kx− λx3.

to the phase plots?

Since we have our program to help us out - answering this problem is rather simple.

All we have to do is to modify the line calculating the acceleration, run the program

again and get figure 3.7b! As can be seen, the phase plots at large amplitudes for the

last two forces hardly differ from each other. This is only to be expected, since the

two forces are nearly identical at large values of x! the difference is readily apparent

for smaller amplitudes, though! Whereas the small amplitue phaseplots are ellipses

centered round the origin for 3.7a, the ellipses for figure 3.7b are centered at a point

displaced to the right. This, of course is the stable equilibrium position +√

for this

force. There should of course be similar ellipses centered around the other point of

stable equilibrium, −√

- can you see why our program misses them?

As is quiet easily apparent, the presence of the unstable equilibrium position at x =

0 makes the small amplitude phase plots differ markedly from an ellipses when the

trajectories get close to this point. To see this more clearly, let us run the program

again, but this time looking at a narrower range of initial positions in more detail. The

result is shown in figure 3.8a. The parameters that were used for plotting these curves

are k = 1 and λ = 0.2.

The fact that phase curves are elliptical near the stable equilibrium position reveals

Page 49: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 17

Figure 3.8: Detailed phase plots of the double well potential. (a) Integration carriedout up to only t = 6.3 - which shows several incomplete curves. (b) Integrating forlonger times gives us the complete phase curves.

itself clearly from the phase plot. What’s perhaps even more apparent is the distortion

in the phase plot as the amplitude gets progressively larger. Something rather dramatic

can be seen when the initial value of x crosses√

10 (this is the value of√

2kλ

) . The

curves which were bounded around the point +√

before this (though not symmetrical

around this point) suddenly change to curves which are centered around the origin now.

This is easily explained in terms of energy - it is only when we set the particle off from

beyond√

2kλ

that it has enough energy to cross the energy barrier at x = 0 so that it

can also go over to negative values of x.

Another new feature reveals itself in figure 3.8a - quite a number of curves seem to be

open curves, as oppposed to the closed ones that we have been seeing so far. A bit of

reflection will show that these are not really open curves - it is just that the curves has

not been given the chance to complete. Remember - the value of 6.3 that we took for

the final value of t in the while loop in the program above was chosen because it exceeds

the time period of the SHO, which was 2π for the parameter values chosen. This was

not a problem for our first nonlinear oscillator - the presence of the λx3 force term only

strengthens the restoring force for larger amplitudes, thereby lowering the time period.

So, all phase plots will have enough time to close before t = 6.3 and then some! The

situation is much the same for our “double well” oscillator for large amplitudes - in this

Page 50: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 18

case the reversal of sign for the linrear term in the force is hardly relevant! Again,

the nearly elliptical paths near the stable equilibrium point have periods of 2π - they

are nearly simple harmonic after all. However, the paths which just “make it over the

energy barrier” have very small velocity over a sizable region - leading to a very long

time period. This can be seen quiet easily in from the fact that increasing the final

value for t in the while loop leads to closed phase space curves, as seen in figure 3.8b.

Note that the red curves represent bound motion around the stable equilibrium position

at +√

, while the green curves represent motion around the other stable equilibrium

point at −√

kλ. The blue curves denote larger amplitude motion where the oscillating

particle has enough energy to “climb the potential hill”!

3.6 Measuring time periods

The discussion in the last section on why some phase plots for the double well potential

looked like open curves leads naturally to the issue of calculating time periods. Of

course, if we start from rest from one point, all we have to do is to go on until the other

turning point is reached - and this will tell us half the time period. So, the problem

boils down to - how to tell when the oscillating particle has turned in its track? The

simplest way to know that this has occured is to check the velocity - if the particle was

travelling to the left before it reached the turning point, it must move towards the right

after that and vice versa. This means that at the turning point, the product of the new

velocity and the old velocity must be negative. Note that this argument is exactly the

same as the one we used to figure out the envelope of damped garmonic oscillations in

section 3.3.

The problem with the way we have written our program so far is that we have assigned

the value of the new velocity to the same variable v - thereby erasing the old one! since

we need to compare the signs of the old velocity and the new one, this will not do. The

remedy is simplicity itself - just store the calculated value of the new velocity in another

variable, say vn, compare the signs of v and vn and before the loop ends, assign the

Page 51: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 19

value of vn to v. the last step is crucial! Forgetting it will give you uniform motion -

no matter what the force - can you see why?

In the following program, we calculate the time period of a SHO for various amplitudes.

The program also prints out the value of the swing - the distance between the two

endpoints.

k = 1.0

m = 1.0

Ai = 1

deltaA = 0.5

Af = 5

A = Ai

while A < Af:

x = A

v = 0.0

t = 0.0

deltat = 0.001

while t < 100:

a = (-k*x)/m

vn = v + deltat*a

if vn*v < 0.0 :

print A,2*t,abs(x-A)

break

v = vn

x = x +deltat*v

t = t + deltat

A = A + deltaA

A new statement that I have introduced in this program is the break statement. This

statement can be used only inside a loop. When the interpreter meets this statement,

the loop is terminated then and there. In this program we have the break statement

Page 52: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 20

in the inner loop of a pair of nested loops. When this is encountered (i.e. when the

velocity changes sign), the inner loop stops and control gets transferred to the line A =

A + deltaA in the outer loop. This not only prevents a wastage of computer time by

stopping the inner loop every time we have found our half time period - it also prevents

the program from printing out multiples of T2

, where the velocity changes sign again!

The rest of the program should be more or less self-explanatory. Once again, let me

point out how apparent python makes the logic of the program via its rigid indentation

structure. Note that we are checking for v*vn<0.0, rather than v*vn<=0.0 - the latter

would have made the loop stop at the very first step2!

Running this program gives an output looking like :

1.0 6.282 2.00000012072

1.5 6.282 3.00000018108

2.0 6.282 4.00000024144

2.5 6.282 5.0000003018

3.0 6.282 6.00000036216

3.5 6.282 7.00000042252

4.0 6.282 8.00000048288

4.5 6.282 9.00000054324

What should be particularly encouraging are the values of the second column - the

program rediscovers what Galileo did centuries ago - the fact that the time period of a

SHO is independent of its amplitude.

Emboldened with this success, we go on to use our program on the nonlinear oscillators.

All we have to do is change the line calculating the acceleration in our last program.

Figure 3.9a shows the fall in time period that we expect for the nonlinear oscialltor

as the λx3 term starts to dominate. On the other hand, figure 3.9b confirms the

2You may be worried that if the new velocity at the end of a time step becomes exactly zero, ourprogram will fail to see the turning point. However, since the time periods involve irrational factorslike π - we will never hit an exact time period by stepping through by deltat - so that is a very remotepossibility indeed.

Page 53: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 21

Figure 3.9: Time period vs. initial position for the nonlinear oscillators (a) for therestoring force −kx − λx3 and (b) for the double well force +kx − λx3. Note the

divergence in the time period as the initial position approaches√

2kλ

where the particle

acquires enough energy to just cross the barrier.

expectation that the time period for the double well force increases dramatically as the

initial position approaches the critical value√

2kλ

.

It is perhaps a better idea to plot the time periods against the energy of the oscillations.

After all - different points of release of the oscillator may correspond to the same energy

(especially for the double well) - and it is ultimately the energy which is directly related

to the time period. Figure shows the dependence of time period on energy for the

two nonlinear systems we have discussed here. Can you see how these curves can be

generated from the data we have alreay used to produce the plots in figure 3.9?

3.7 The good old pendulum

When we mention oscillators the first one that usually springs to mind is the simple

pendulum - which has had a stellar role in the history of our subject. Every schoolboy

knows that a simple pendulum of length l has a time period given by

T = 2π

√l

g

Page 54: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 22

(a) (b)

Figure 3.10: Dependence of the time period of oscillation on the energy for (a) thenonlinear oscillator with restoring force −kx − λx3 and (b) the double well potentialwith restoring force +kx− λx3.

- a slightly more advanced student knows that this is actually an approximation. The

actual differential equation describing the simple pendulum (provided you can ignore

the effect of friction etc.) is

ml2ϑ = −mgl sin ϑ

or

ϑ +g

lsin ϑ = 0

This is not the SHO equation - but if ϑ is sufficiently small, the value of sin ϑ is nearly the

same as ϑ, which is why the motion of the pendulum is approximately simple harmonic.

This approximation is what made your teachers exhort you to keep the amplitude of

the pendulum below 4◦.

How does the nature of motion change when we stop keeping the amplitude within these

small limit? One way to answer this question is to carry out the experiment on a real

pendulum. Another is, of course, is to use simulations so that the computer effectively

carries out the experiment for us. We can easily modify our programs to calculate the

phase space trajectory and the time period of the pendulum.

All that we need to do is to change the line calculating the acceleration to

Page 55: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 23

Figure 3.11: Simple pendulum - (a) phase plots and (b) variation of time period withamplitude

a = -g/l*sin(x)

of course after defining the parameters g and l near the beginning of the program. This

brings us to our first python function - the sin(x). We will discuss a lot more about

functions in chapter 4. For the time being, just note that it is being used just as the

mathematical function sin (x). There is one more thing that you have to do before this

program can be used, though. Before the function is called for the first time (i.e. before

the interpreter reaches the line above) - preferably near the beginning of the program

you must add the line

from math import *

don’t worry about what this means just yet - we will discuss this in gory detail in section

4.1.

Figure 3.11a shows the phase plots that you get for the pendulum. To get the phase

plots, what I did here is to start with Actually, I used a trick to get gnuplot plot the

entire curve for you - the program will give only half of the figure directly! Note once

again that the small amplitude phase plots are rather neatly elliptical - distortions

showing up only at large amplitudes. All the phase curves for trajectories starting with

Page 56: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 24

small speeds are closed curves, marked in red in the figure. These tracks correspond to

oscillations of the pendulum - the kind of swinging motion we usually associate with a

pendulum. Starting the motion with rather large velocities gives us the purple colored

phase plots - open curves where the coordinate ϑ keeps on increasing in size. These

curves describe what is known in the technical language as libration of the pendulum.

In plain English, all this meansis that the pendulum has been given such a high initial

speed that it does not swing but keeps on going round and round (ϑ keeps on rising,

but remember that ϑ + 2π is the same angle as ϑ!). The line in blue that separates

these two kinds of motion - this represents the situation where the pendulum has just

enough energy to complete a full circle. This is called the seperatrix. structures like

these play a role of immense importance in modern classical mechanics.

Figure shows the variation of the time period of the pendulum with the amplitude in

radians. As you can see, there is an increase in the timeperiod with amplitude. On the

other hand, you can see that the increase is not really very serious for even moderate

angles - a 1% increase in the time period actually takes as big an amplitude as about 0.4

radians - that’s nearly 22◦! So, it doesn’t really hurt to increase the amplitude beyond

the 4◦ limit - as most schoolboys know from experience!

3.8 Forcing the oscillator

All real oscillators have at least a bit of damping. That’s why all oscillations tend to

die down in real life. One way to keep an oscillation going is known to every little

kid on a swing - you must give the oscillator a periodic push. This means that our

oscillator must be subjected to a periodic external force. The simplest such situation

mathematically occurs when this force is a pure sinusoid of the form F0 sin (ωt). In

this case, the differential equation governing the motion of a damped forced harmonic

oscillator is

md2x

dt2= −kx− γv + F0 sin (ωt) (3.1)

The reason why we give this case so much importance ultimately lies at the door

of a theorem in mathematics called Fourier’s theorem - which says, roughly, that

Page 57: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 25

any reasonable periodic function can be written as a sum of several (maybe infinite)

sinusoids. Since the equation (3.1) is a linear one - this means that if we understand

the response of our oscillator to individual sinusoids, we can figure out the response to

any periodic waveform whatsoever!

(a) (b)

(c) (d)

Figure 3.12: Plots of position and velocity versus time for the damped harmonic oscil-lator (a) x vs. t for ω = 0.3ω0, (b) v vs. t for ω = 0.3ω0, (c) x vs. t for ω = 0.8ω0,(d) v vs. t for ω = 0.8ω0. Note the increase in the amplitudes as the driving frequencyapproaches the resonance frequency.

Making the necessary changes to our SHM program, which involves just adding the

extra term to the line of code calculating the acceleration (and adding the magic line

from math import sin at the beginning of the program) we are in a position to explore

the behaviour of the forced oscillator. Figure shows the variation of x with t, and v

Page 58: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 26

with t for a forced harmonic oscillator where ω = 0.3ω0. As you can see, for very small

times the behaviour is almost that of a damped harmonic oscilaltor - but very soon

the damped natural oscillations die down, leaving behind a steady forced oscillation.

This oscillation, which persists forever once the initial transients have died down, have

the same frequency as the forcing vibration - as you can easily check. Figures show

the same plots, but this time with ω = 0.8ω0. As you can see, this time the steady

oscillations are much larger in size. This leads us to an important question, just how

do the steady state amplitudes depend on the driving frequency? We will investigate

this in a while - but first let us see what happens to the oscillations when one changes

the initial conditions.

(a) (b)

Figure 3.13: Forced oscillation at ω = 0.8ω0 with different initial conditions - x = 0and v = 0 at t = 0. (b) Shows the same graph superposed with that for the previousinitial condition x = 10 and v = 0 at t = 0. Note that once the transients die down,the oscillator displays the same motion in both the cases.

Figure 3.13a shows the variation of velocity with time for the same harmonic oscillator,

but this time we start the oscillations with the oscillator at rest at the equilibrium

position. If you compare this with figure 3.12b, it is immediately apparent that the

initial motion in the two cases are quiet different. This is no surprise - after all, the

detailed nature of motion does depend on the initial conditions! What is striking,

though, is that the long time behaviour shown by the two curves are identical! This

can be seen even more clearly in figure 3.13b. You can try playing around with a few

Page 59: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 27

more initial conditions to convince yourself that this is generic - the long term behaviour

is independent of the initial conditions.

We have seen, though that the steady behavious that emerges in the long run does

depend on the driving frequency - it definitely depends on the driving frequency. It

turns out that it also depends on the amount of damping present in the system. To

investigate these dependences, we can turn to programming once again. By using loops

inside loops - things that are called nested loops in the jargon, we can easily vary γ

and ω over prescribed ranges, and investigate the motion for each combination of values

taken by these quantities. The structure will look like

gamma = gammai

while gamma < gammaf:

w = wi

while w < wf:

x = 0

v = 0

t = 0

... code calculating motion here

w = w +dw

gamma = gamma + dgamma

Note that the initialisation of the value of w will have to be done inside the first while

loop that loops over the values of gamma. The value w takes after a single run of the

inner loop is over is more than wf. If the line w = wi had been outside the outer loop,

then on the second run of the outer loop the value that w would have started with would

have caused the inner loop to terminate without running even once!

Just what shall we calculate inside the inner loop? Of course, we will have to carry out

the Euler algorithm for calculating v and x - which means yet another loop - this time

over the value of t! However, getting your program to print out the value of t, v and

x for each step will be a bit of an overkill! What will be do with such a huge mess of

data? All we need to know, after all, is how big the steady state oscillations are going

Page 60: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 28

to be. So we need to figure out, for each ω and γ what the maximum values attained

by v and x are. A program that can do just this is as follows :

from math import *

# set parameters

m=1.0

k=1.0

gammai=0.1

gammaf = 1.0

dgamma = 0.1

F0 = 2

wi = 0.3

wf= 3

dw=0.01

#set time limits

ti=0.0

tf=100.0

deltat=0.001

# the actual calculation

while gamma<gammaf:

w = wi

while w<wf:

t=ti

x=0.0

v=0.0

while (t<tf/2.0):

a = (-k*x-gamma*v+F0*sin(w*t))/m

v = v + a*deltat

x = x + v*deltat

t = t + deltat

vmax=0

Page 61: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 29

amax=0

while (t<tf):

a = (-k*x-gamma*v+F0*sin(w*t))/m

v = v + a*deltat

x = x + v*deltat

t = t + deltat

if v >vmax:

vmax = v

if a >amax:

amax = a

print w,vmax,amax,gamma

w = w + dw

print

gamma = gamma + dgamma

We have not used any new features of the python language in this program. However,

we have used a new programming trick - one that allows us to figure out the maximum

value a quantity attains. Note that all we have done is assign the value 0 to a variable

vmax to begin with - and then kept on checking whether the current value of v exceeds

that stored currently in vmax. If it does, we change the value of vmax to this value of

v. It should be obvious that at the end of the loop, the value stored in vmax should

be the maximum value that v attains during the loop - which is just what we need! A

word of warning though - this assumes that the value of v exceeds the initial value of

0 chosen for vmax - if not, the value of vmax will remain at 0. In this case, we know

that the value of v swings to both sides of 0 - so our method will work. In case we

want to find the maximum value attained by a variable for which this information is

not available - we may try starting with a very small (actually large sized but negative)

value for the variable that is to store the maximum value - one that is guaranteed to

be smaller than the maximum attained. In our prgram, we have done the same thing

with another variable amax too - this stores the maximum value of the displacement,

just as vmax stores the maximum value of the velocity.

Page 62: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 30

(a) (b)

Figure 3.14: Resonance in a driven damped oscillator (a) velocity resonance and (b)amplitude resonance. Note that velocity resonance occurs at exactly ω = ω0 whereasamplitude resonance occurs at a slightly lower frequency. This difference, however, ishardly noticable in the graph above - but it rises with increasing damping. The greenlines in both figures are fits of the data with the theoretically predicted values.

Figure 3.14 shows the results of our program, where the output is plotted at a fixed,

rather small value of γ. What immediately catches the eye is that the response increases

with ω at low frequencies, reaches a peak and then falls off. The fact that a driven

oscillator responds the most when the driving frequency is just equal to the normal

frequency of the system (at least as far as the velocity response is concerned) is called

resonance. A similar feature is also seen when the amplitude is plotted versus the

frequency. As can be seen from figure 3.15, the resonance curves becomes smaller in

height and flatter as the damping rises.

The irregular variations that can be seen near the tail of the resonance curves above

are not real effects - they are artefacts of the computation process. One of the major

reasons why they arise is the fact that the algorithm we have been using is not a very

accurate one. In a later chapter, we will learn more accurate algorithms which will

help us get rid of these artefacts to some extent. For the time being, do note that our

results are correct in the most part - as can be seen from the excellent match between

the theoretical curves and calculated points in figures 3.14a and 3.14b.

Just for the record, the theoretical curve that expresses the variation of the maximum

Page 63: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 31

Figure 3.15: Resonance curves for a damped harmonic oscillator. Note that the curvesbecome progressively smaller in height and flatter as the damping rises.

Page 64: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 32

velocity with frequency is given by

vmax =1√(

γmω0

)2

+(

ωω0− ω0

ω

)2

F0

mω0

(3.2)

Which clearly shows the maximum at ω = ω0. Also, the peak value is seen to be F0

γ-

which is why the curves diminished in height as the damping increased.

3.9 Forced vibrations without damping and nonlin-

earity

We have seen above that the forced vibration response falls off as the damping rises.

What about the other way around? What happens to the motion when no damping is

present? Of course, we could analyse these results analytically - but we have our trusty

program to hrelp us out in this matter. All that is required is to set γ = 0 and let the

program do the rest.

(a) (b)

Figure 3.16: Forced undamped harmonic oscillator with the initial conditions x = 0, v =0 (a) with ω = 0.8ω0 and (b) with ω = 0.95ω0. Note the beats that occur between theundamped natural oscillations at frequency ω0 and the driven oscillation at frequencyω.

Page 65: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 33

(a) (b)

Figure 3.17: Undamped oscillator at resonance.

(a) The linear oscillator. Note the linear growth in the oscillations with time. (b)With a small bit of nonlinearity thrown in, we see beat like structures.

Figures 3.16a and 3.16b shows the variation of the velocity with time for the undamped

oscillator starting from rest under a damping force having frequency ω = 0.8ω0 and

ω = 0.95ω0. As can be readily seen, the oscillations increase as one gets closer to

resonance (If that is not obvious at first glance, take a closer look at the two velocity

scales). What is perhaps even more striking are the beats that you can see. Without

damping, the natural oscillations that occur in the system do not die down - and thus

the system does not really settle down to a steady oscillation that happens in the

undamped case. As the driving frequency approaches the natural frequency, the beat

frequency decreases - an effect that can be easily seen in figure 3.16b.

What if the system is exactly at resonance? Equation 3.2 seems to suggest that the

amplitude of the velocity oscillations will be ∞! That’s nonsense, of course! So, what

does really happen? Let’s ask our friend the computer. The result is shown in figure

3.17. The velocity does not really have an infinite amplitude - but it does have an

amplitude that grows linearly with time.

Come to think of it - with hindsight it seems pretty easy to figure out that this should

be the case. Note that as the driving frequency gets closer to the natural frequency the

Page 66: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 34

beats get lower and lower in frequency - so that the decrease in amplitude occurs later

and later. In the limit, when ω = ω0 the decrease does not occur at all!

Now that we know what happens at resonance if there is no damping - the natural

question to ask is - “does this really happen?” One reason why this does not sugests

itself immediately - in real life rthere is always some damping - no matter how small.

However, there is another reason why the velocity does not keep on growing forever as

seen here. After all, remember the −kx restoring force is usually an approximation,

valid only when x is small. When the displacement becomes large, the nonlinear terms

like −λx3 comes into play. The increasing restoring force halts the growth of oscillations

- leading to beat like structures again - as can be seen from figure 3.17b.

3.10 Parametric resonance

So far we have talked of resonance in the context of the externally driven oscillator

- one in which a periodic driving force is imposed from the outside. This makes the

equation of motion of the oscillator explicitly time dependent and leads ultimately to

the phenomenon of resonance. Another situation where the oscillator may have an

explicit time dependence is is when one (or more) of the parameters that describe the

oscillator (like the mass, the spring constant or the damping factor) depend explicitly

on the time. The oscillations demonstrated by such a system are called parametric

oscillations.

One common example of a parametric oscillator is a situation where the force constant

varies with time. In many common situations, this time dependence is of the form

k = k0 (1 + h cos (ωt))

where h characterizes the size of the usually small variation of the force constant with

time. The equation of motion of such an oscillator is given by

md2x

dt2+ γ

dx

dt+ k0 (1 + h cos (ωt)) x = 0 (3.3)

Page 67: Programming for Physics in Python

CHAPTER 3. INVESTIGATING OSCILLATIONS 35

When the point of suspension of a pendulum is externally driven to vibrate at a fre-

quency of ω the motion of the bob obeys an equation like the above. This goes to show

that this is not a very contrived example!

Modifying our dynamics program to take care of this new system is a rather trivial task

- all you have to do is to change the line calculating acceleration to

a = -(k0*(1+h*cos(w*t))*x + gamma*v)/m

and your program will be ready to examine parametric oscillations. You can change the

values of the various parameters m, k0, h, ω and see how the system response changes.

As the frequency of the variation in the spring constant changes, the system exhibits

the phenomenon of parametric resonance. In particular when ω = 2ω0 where ω0 =√

k0

m

the velocity and position amplitudes show an exponential growth under damping free

conditions - as is shown in figure . As the damping grows, the rate of this exponential

growth comes down as expected, until beyond some threshold value of γ the amplitudes

tend to come down with time. These features can be clearly seen in figures .

Page 68: Programming for Physics in Python

Chapter 4

More Python - improving the

dynamics program

Now that we have used the original drag.py program and it’s cousins often enough to

get the hang of things, let us try to figure out how to improve on it. On the way, we

will learn several other aspects of programming in python.

4.1 The math module

One of the first things that might spring to mind is how to be really sure about the

accuracy of our program. Of course, your gnuplot plot looked almost the same as the

exact one - but that does not give us a precise notion of the errors involved. Since we

know the exact result in this case, it should be pretty easy to modify our program so

that it manages to tell us not only the calculated value of the velocity, but by how much

it departs from the exact value as well!

How do we calculate the exponential that is there in the exact result for the falling

body under viscous drag? Not to worry - python contains a large library of standard

functions which can be used for common programming tasks, including calculating the

1

Page 69: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 2

exponential of some argument x (As we will see soon, you can and very often will also

create your own).

A function is just some Python code which is separated from the rest of the program.

This has several advantages: repeated sections of code can be re-used without rewriting

them many times, making your program clearer. Furthermore, if a function is separated

from the rest of the program it provides a conceptual separation for the person writing

it, so she can concentrate on either the function, or the rest of the program.

To use a function in a program we call it. To do this you type its name, followed by the

required parameters enclosed in parentheses. Parameters are sometimes called argu-

ments, and are similar to arguments in mathematics. In maths, if we write exp(x) it is

clear that we are asking for the exponential function to be evaluated with the argument

x - and the same goes for python. As in mathematics, again, a particular argument

may be an expression. The interpreter first calculates the value of the expression, then

passes it on to the function as the argument.

It should be almost obvious that to calculate the exact value for the velocity, all you

need to do is to add the line

vex = m*g/k*(1-exp(-k*t/m))

to the body of the loop, and also change the print statement to

print t,v,x,v-vex

Try running, this program and you may be in for a surprise - instead of meekly sub-

mitting to your command, the interpreter has actually thrown an error

NameError : name ‘exp’ not defined

This may come as a shock to all of you - after all, if python is really that powerful

a language, then how come it does not even have a command for the exp function?

Page 70: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 3

Before you give up on python in disgust, let me rush to reassure you - python does

have the means to calculate the exponential function, indeed it has many, many more

functions in its repertoire. However, a very conscious decision, which was taken to keep

the structure of the language simple, was to modularize it - so that most of pythons

specialized capabilities are part of separate modules, which are to be loaded as and

when required. A module is a collection of functions, definitions and data that are

provided as a package.

The mathematical functions that most people need are part of a module called, rather

unimaginatively, math (This, of course, is the module that we are going to need most

often). So, before you can invoke the mathematical functions that python provides you

with, you must tell the interpreter to use the math module, by adding the line

import math

before the mathematical functions are used. Once you do that, however, there is another

small shock waiting for you - the exponential function is not called exp even now, it is

called math.exp! So, the line to calculate the exact value of the velocity must be

vex = m*g/k*(1-math.exp(-k*t/m))

Keeping on calling mathematical functions by putting a “math.” in front of their names

may seem to be a bit of a bother, but there is a very good reason for that. For

some reason or the other, you may have to write a function called exp yourself in your

program. Then, whenever you refer to exp, the interpreter will call your function, and

the only way in which you can refer to our good ol’ mathematical exponential is to call

it using “math.exp”. If this seems too much of a bother to you - there is a way around.

Instead of just saying import math, you can use the command

from math import exp

and you can henceforth refer to exp, without having to prepend the “math.” Indeed, if

you wish to refer to quite a few functions that are available in the math module, you

may as well say

Page 71: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 4

from math import *

in which case you can refer to all the functions that math provides you with (and there

are quite a lot of them!) directly.

Now that you have modified your program to tell you the error in your calculations,

put this to use. Run the program by using

$ python drag.py > drag.out

Fire up gnuplot and say

gnuplot> plot ’drag.py’ u 1:4

and you will get a plot of how the error varies with time. Do not feel discouraged by

the rather large peak that the graph shows - just take a look at the scale alongside.

Remember, gnuplot autoscales the graph - unless explicitly asked not to. You may enjoy

playing with the values of deltat to see how the error changes in response to varying

deltat.

Do you want to know what goodies are available in the math module? Well, you can

always read a good python book - but there is a simpler way. Just fire up the python

interpreter (if you are using IDLE, its just the python shell that it began with), and at

the prompt type

> > > import math

> > > help(math)

Here you meet another python function - help. It does just what you would think it

does - provide you with help on the python language. Indeed, python has one of the

best online documentations among all programming languages, so by all means use it!

You may be rightly wondering why we did not have to refer to the module that con-

tains the help function while calling it. The answer is that python has a module called

Page 72: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 5

__builtin__ (Note the name carefully, that’s two underscore characters each at the

front and the back of the name!), and all functions belonging to that module are imme-

diately available to you from the moment the interpreter is called - as if you had issued

the command

from __builtin__ import *

at the very beginning. You will meet more and more such builtin functions as you keep

on programming in python. By the way, if you were to ask for help on help itself, the

function would modestly reply :

Help on instance of _Helper:

Type help() for interactive help,

or help(object) for help about object.

You may want to type help() and then follow up at the help> prompt by typing

modules just to see what modules are available for you to play around with - you may

be in for a surprise! In case you want any more, there are thousands of modules written

by other programmers floating around on the internet. Indeed, it is a very standard

joke in python circles that it is a waste of time to program in python - all that you need

to do is to borrow somebody else’s module that has been written to perform precisely

the task that you want to do!

By the way, there is another module that is always imported by python on startup -

that is the exceptions module. That’s the one that gives you all the nasty messages

when you mess up. So now you know who to blame when python throws a NameError

at you!

Getting back to the math module, the response that the interpreter gives to our

help(math) command is huge - a little bit of which is reproduced below, just to give

you a flavor:

Page 73: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 6

Help on module math:

NAME

math

FILE

/usr/local/lib/python2.3/lib-dynload/math.so

DESCRIPTION

This module is always available. It provides ac-

cess to the

mathematical functions defined by the C standard.

FUNCTIONS

acos(...)

acos(x)

Return the arc cosine (measured in radians) of x.

asin(...)

asin(x)

Return the arc sine (measured in radians) of x.

...

...

exp(...)

exp(x)

Return e raised to the power of x.

...

...

tanh(...)

tanh(x)

Return the hyperbolic tangent of x.

Page 74: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 7

DATA

e = 2.7182818284590451

pi = 3.1415926535897931

You don’t have to memorize all this - but do try to remember (at least roughly) what

is available. Just to impress you a bit further, let me tell you that if you ever were to

need more computing power, you can try importing the Numeric package (if you have

it installed, if not you can always download it from the net) - this gives you almost all

the capabilities of the highly expensive MATLAB program, and all for free!

4.2 Defining your own functions

Now that you have learned about using the functions that python provides you with -

it is time to begin writing some of your own! However, we are not going to write our

own functions just for the heck of it - the reason why we will bother with this is that

it will make our program a lot better. If you take a look at the logic of your program,

you should be able to immediately pick out at least two spots where modularization

will help. As you have seen, switching from one dynamical system to another means

changing the force law, so it will help to calculate the force separately. In this way, when

you want to study some other system, all you have to do is to change this function.

Again, as we will see soon, the simple minded algorithm that we have used to update

the values of position and velocity is OK for qualitative purposes, but we may need to

use more advanced methods for higher accuracy. So it will be useful if we can write a

separate update function that does the job of updating things, so that when we change

algorithms all that we have to do is change that function. We will later meet many

more reasons why you may want to write your own functions - but these will suffice for

now.

First, a function that calculates the force. What name can we give such a function?

It turns out that all valid variable names can also serve as the names of functions (of

course as long as there is no clash) - so force is as good a name as any! We will also

Page 75: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 8

need to know what arguments our force function depends on. In this case, the force

depends on the velocity, but in more general situations, it may also depend on position

and time. To keep generality, we will write a force function that depends on all three.

We will call the function to calculate the acceleration, so that the beginning of our loop

will now look like

while (t<tf):

a = force(v,x,t)/m

But how do we define the function in the first place? Rather obviously, the keyword

that allows us to define a function is called def, and the syntax for defining the force

function is:

def force(vel,pos,time):

f = m*g - k*vel

return f

Since the definition is a compound sentence, it has a header line - the def statement,

and an indented block of sentences where the actual function is calculated.

The def statement in this case defines a function force with arguments vel, pos, time

. Note that the arguments are really local place holder variables - they spring into being

when the function is called, and take the value of the calling arguments in order. If you

call force as force(v,x,t), vel gets the value currently stored in v, pos gets the value

in x and time gets the value in t. The line f = m*g - k*vel calculates the variable

f, and the next line tells python to return the value of f as the result of using this

function.

Note that the variable f is local to the function block - it comes into being when the

function is executed, and disappears when the function evaluation has finished. If you

try to refer to f from the main part of the program (where you called force to begin

with, or any other part for the matter) - you will land up with a NameError! The

Page 76: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 9

same actually goes for the arguments vel, pos and time. On the other hand, you may

have already defined another variable called f in the main program block, in which case

you can keep on happily referring to it there, without having to worry about its value

changing when the function is evaluated.

The above brings us to the issue of namespaces. This is rather involved - so we will

devote a whole section to this later. Roughly, however, the rule is that whenever a

variable is defined in a block, it is available to all the pieces of the program being called

from there, and their sub-programs and so on. That is why we could merrily refer to m,

g and k without having to pass them as arguments to the function force. As long as

they are defined in the calling program block, python will merrily take their values and

calculate any expressions that are present in the body of the function. On the other

hand, if your function had been defined as

def force(vel,pos,time):

g = 10.0

f = m*g - k*vel

return f

the expression f would have been calculated with the value given to local variable g ,

namely 10.0 - rather than 9.81. On the other hand, the fact that you decide to call this

variable g does not in any way change the value of the variable that you had called g

in the main program block! Add a print g statement after the function call, and you

will see that the value of g in the main program remains 9.81.

For the time being note that the function force could have been defined with the

arguments v, x and t, i.e. the defining lines for the function may have been written as

def force(v,x,t):

f = g - k*v/m

return f

Page 77: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 10

What you should pay attention to is that the arguments v, x and t here are place

holders - the same as vel, pos and time in our original version. In particular, they

are local variables, and not the same as the global variables with the same names that

we were using in the outer blocks. Just to check, change the definition of the function

to

def force(v,x,t):

f = g - k*v/m

v = 0

return f

and see whether this makes any difference to the output of your program!

Just to make the distinction between local and global variables clearer, let us just use

the python interpreter to exchange the values of two variables. Fire up the interpreter

by typing python at the command line - and then type

> > > x = 10

> > > y = 5

> > > temp = x

> > > x = y

> > > y = temp

> > > print x

5

> > > print y

10

Wondering why we needed to bring up the variable temp? Think it out yourself! Now,

let’s try to relegate the job of swapping the values of the two variables to a function

that I am going to call swap() :

> > > x = 10

Page 78: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 11

> > > y = 5

> > > def swap(x,Y):

... temp = x

... x = y

... y = temp

... print ’x is ’, x

... print ’y is ’, y

... return

> > > swap(x,y)

x is 5

y is 10

> > > print x

10

> > > print y

5

Note that despite the fact that we have carried out the same steps inside the function

swap, as we did before, this time the variables have not been swapped! As the print

statements issued form inside the function shows, the function really managed to swap

the variables x and y local to it - but that does not affect the global variables x and y

in any way1!

Now, let’s come to the function that updates the values of velocity, position and time

- the one that actually carries out all the hard work in our program. Let me first write

down the function :

1This may let you wonder how you can get the simple task of swapping two variables done at allby means of a function. Once you learn a bit more about namespaces, and the details of how pythonhandles global and local variables - you will be able to write a correct program for doing this. However,such a function is not necessary at all - since python gives us such a beautiful way of doing it! All youhave to do is

x, y = y, x

- try it for yourself and check!

Page 79: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 12

def update(v,x,t):

a = force(v,x,t)/m

v = v + a*deltat

x = x + v*deltat

t = t + deltat

return v, x, t

Note that a function can call another one - here update is calling force. Also, the major

difference in this case is the fact that it return three variables instead of one! The

important point is that the calculations carried out inside the body of the function does

not affect the values of the global variables v, x and t. However, in this case we do

want to update the values of the global variables! The way out is to make the function

return the values of three local variables v, x and t, which we did in the last line (note

that when you want your function to return more than one variable all you have to

do is to specify a list of the desired variables, separated by commas. What this really

returns is a tuple - a data type that we will meet later - don’t worry about that now!).

When we call the function, we will say

v,x,t = update(v,x,t)

What this does is that it passes the values of the global variables v, x and t to the

function update, where they are stored in the local variables v, x and t and worked

upon. Finally the function returns the values of the local variables v, x and t which

are assigned to the global variables v, x and t. Whew! That’s quite a lot of work!

What you should realize is that the local variables v, x and t are quite distinct from

the global ones, identical names notwithstanding. Maybe we could just have called

them something different to begin with (the way we had called the variable vel in

our function force) - that would certainly have ruled out this confusion. However,

sometimes the logic of the program is clearer if we use the same names for variables

that are logically equivalent - as is the case here.

So, with all these improvements, what does our program look like now?

Page 80: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 13

import math

def force(vel,pos,time):

f = m*g - k*vel

return f

def update(v,x,t):

a = force(v,x,t)/m

v = v + a*deltat

x = x + v*deltat

t = t + deltat

return v, x, t

# set parameters

m=1.0

g=9.81

k=1.0

#set initial conditions

ti=0.0

tf=10.0

deltat=0.001

t=ti

x=0.0

v=0.0

while (t<tf):

v,x,t = update(v,x,t)

vex = m*g/k*(1-math.exp(- k*t/m))

print t, v, x, v-vex

Note how writing the functions has simplified the body of your while loop. This may

Page 81: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 14

not seem much now, but if you modify your program to handle more complicated force

laws, you will certainly reap the benefits of what you have done just now.

4.3 Asking the user

A major shortcoming of our program that you may have noticed is that it hard codes

the values of the parameters used, as well as the initial conditions. So, each time you

want to see the effect of changing a parameter, you have to directly edit the program.

Of course, this is less of a bother for an interpreted programming language like python

than it is for, say, C - where you will have to recompile the source code every time you

change it! However, you may find it a much better experience, if the program took its

parameters from you while running. To illustrate this idea let us see how we can modify

our program so that you can supply the value of the mass at runtime. Taking input

from the keyboard requires you to use another of python’s builtin functions, called,

rather obviously, input. All you do is replace the line m = 1.0 by the line

m = input()

Note that input is a function, so even if you were to call it without any arguments, as

is the case here, you have to keep the braces ( ). If you now run the program by typing

python drag.out

the program responds by - doing nothing! All you will see is a cursor sitting pretty,

staring at you. Of course, all that the computer is doing is waiting for you to give it

the desired input - so if you type in the value 1.0 (or any other floating point value you

desire), the program will merrily continue to print the output lines. One drawback of

this approach is apparent here - you who have written this program may understand

that the computer is waiting for you to supply the mass (if you were running the

program a month after writing it, I wouldn’t bet on even that!) - but what if someone

Page 82: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 15

else were to run it? What if more than one input value was asked for - not only will

the poor user have to remember them all, he or she would have to supply them in the

precise order in which they are called for in the program!

A better alternative is provided by the input function itself. Remember, input is a

function, so it takes in argument(s). The argument that input takes is a string, which

the input function uses as a prompt for the user. So, instead of the line m = input(),

use the line

m = input(’Give me the mass : ’)

in your program, and calling python to execute it will lead to

$ python drag.py

Give me the mass :

Type in the value of the mass and hit enter - you will see the program spewing out its

results to the screen.

You must have noticed that in this section we have not redirected the output of the

program to a file, so that the entire output appears on the screen. This is not very

convenient, as we have already seen - so why not try the redirection trick mentioned

earlier? Type

$ python drag.py > drag.out

at the command prompt and you will see - just a cursor sitting there staring at you!

What happened to the nice little prompt that we had written? A little thought will

tell you the answer - the prompt was showing up on the standard output - the screen;

and now you have redirected the standard output to the file drag.out. Just type in

the value of m, hit enter and in no time at all, the command prompt comes back. Open

up the file in your favorite editor and there, sure enough, will be the line Give me the

value of m : - sitting on its very first line! Trying to plot the output in gnuplot leads

to trouble too :

Page 83: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 16

gnuplot> plot ’drag.py’

^

Bad data on line 1

that’s understandable - gnuplot wants numbers on the first line (as well as every other

line) - what it gets is words! You can use your editor to add the # character in front

of the offending line - in gnuplot as in python, # signals a comment - so that gnuplot

will simply ignore that line while plotting.

Of course, redirecting the standard output is no longer a good idea - because it makes

you miss out on the nice helpful prompts that you may write for your inputs. It would

be much nicer if one can ask the program to directly write its output to a specified

file - while writing the prompts etc. to the screen. A more complicated program may

produce several different kinds of output and it may be a good idea to ensure that the

program writes them on different files on its own. In the next section we will touch on

the issue of file handling - especially that of writing on files.

4.4 Handling files in python

To set the stage of learning to handle files, let me quote Thinking like a Computer

Scientist in Python

Working with a file is a lot like working with books. To use a book,

you have to open it. When you are done, you have to close it. While the

book is open, you can either write in it or read from it. In either case, you

know where you are in the book. Most of the time, you read the book in its

natural order, but you can also skip around.

All of this applies to files as well.

What we want to do in our program is to open the file drag.out so that it can be written

to. In order to do this, add the line

Page 84: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 17

f = open(’drag.out’,’w’)

near the beginning of the program. The function open takes two arguments, both of

which have to be strings. The first argument specifies the name of the file to be opened,

while the second specifies the mode. In this case the file that we want to open is

drag.out and the ’w’ tells us that we are opening it to write to it. The result of using

the open function is an object. Those of you who are meeting objects for the first time

can just think of f as a shortcut to the file data.out - we will have a lot of discussion

on objects and OOP (object oriented programming) later.

At this stage we have the file drag.out open and ready for us to write on. Python

provides us with a very simple way of writing to this file. All that you have to do is use

the print command as before - just remember to redirect the output to the file drag.out

and not the standard output. We have already been doing this from the unix prompt -

but here is a way to do this from within your program itself. All that you have to do is

use the same > > symbol that you used for redirection earlier, but this time inside the

print command itself! So, your print line now looks like

print > > f,t,v,x,v-vex

and the entire contents of the variables t,v,x,v-vex will be written to the file that

the variable f points to, namely drag.out. The advantage of redirecting the output of

individual print commands as opposed to the whole output (as we were doing earlier

from the command line) is obvious - we can still have the messages to the user showing

up on screen, while rediercting specific lines of output to the specified file(s). For more

complicated programs, we can have various kinds of output and it may be a good idea

to write them to different files. This is something you could not achieve by redirecting

from the shell - all of the output will land up in the same file. From within the program,

of course, you can open several files at the same time and just tell the interpreter where

you want a particular line of output to go - easy!

You should close all the files that you have opened when you are done with them - so,

after the printing is over, you should have the line of code

Page 85: Programming for Physics in Python

CHAPTER 4. MORE PYTHON ... 18

f.close()

whose job is to close the file being referred to by the object f, which is the file drag.out

in this case. Note that close() is a function, so you have to have the () - even though

in this case we don’t have any arguments to pass and so the () is empty!

Page 86: Programming for Physics in Python

Chapter 5

Root finding

In chapter (2), we have seen several examples of how you can use the computer to

explore the dynamics of a particle. In the next few chapters we will see a few more

examples of simple programs that can help us explore different aspects of physics. Of

course, I am making no attempt whatsoever at being exhaustive - all I want to do is

give you a flavour of what can be done. In the current chapter we will discuss a problem

which belongs more directly to the realm of mathematics, but which crops up time and

again in various physics applications. This is the problem of root finding - the problem

of finding solutions of equations of the form f (x) = 0.

5.1 Warmup - the quadratic equation

One of the first non-trivial equations that we learn to solve is the quadratic equation

ax2 + bx + c = 0

Every schoolchild knows the solution to this equation

x =−b±

√b2 − 4ac

2a

1

Page 87: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 2

Of course, the equation reduces to a linear one if a = 0 in which case there is only one

root x = − cb

(again, if b = 0 on top of this, we get either a trivial solution or if c = 0

and an inconsistent equation if c 6= 0). This is too simple a problem to really require

the writing of a computer program to solve this. Having said this, writing a program

to solve this can be quite a good exercise - especially if you try to take all the special

cases into account. A possible program could be this

# Program for solving the quadratic equation

# a*x**2+b*x+c=0

from math import sqrt

print ’This equation solves the quadratic equation’

print ’\n\n’

a = input(’Enter the coefficient of x**2’)

b = input(’Enter the coefficient of x’)

c = input(’Enter the constant term’)

if a==0:

if b==0:

if c==0:

print ’Trivial equation, satisfied by any x’

else : #if c is not0

print ’Inconsistent equation, no solution!’

else: #if b is not 0

print ’Linear equation, solution is’

print ’\t\t’,-c/b

else: #if a is not 0

d = b**2 - 4*a*c

if d>0:

r = sqrt(d)

print ’Two distinct real roots’

print ’\t’,(r-b)/(2.*a),’\t and \t’, (-r-b)/(2.*a)

Page 88: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 3

elif d=0:

print ’Two equal roots’

print ’Both are \t’,-b/(2.*a)

else:

r=sqrt(-d)

print ’Two complex roots’

print ’\t’,-b/(2*a),’+ j’,r/(2*a),’ and’

print ’\t’,-b/(2*a),’- j’,r/(2*a)

I hope that you will take home two lessons from this simple program. First, if you

want to cater to all possibilities, even a program like this is going to get quite involved.

Secondly, the rigid rules about indentation obeyed by python atually makes it pretty

easy to understand (at least, I hope you feel that way!) the structure of a program that

has quite a lot of compound statements.

The formula for the roots in of a quadratic equation in terms of the coefficients is, of

course, quite familiar. What may not be so familiar is the fact that similar formulae

for roots can be written down for both cubic and quartic equations - although the

corresponding formulae are a lot more complicated. Indeed, it was proven by Galois

that no such “solution by radicals” do not exceed for any general polynomial equation

above degree 4. This does not mean that roots of such equations do not exist - just

that you can not write down a direct formula for them in terms of the coefficients of the

equation. After all, the fundamental theorem of algebra asserts that any polynomial

equation of degree n has precisely n roots in the set of complex numbers (provided you

count any multiple roots). Of course, polynomial equations are not the only posible

kind of equation that one may have to worry about - and there are quite a few equations

for which we may not even be sure beforehand whether solutions exist or not. In all

such cases, the computer is actually a godsend as far as the task of root finding is

concerned.

Page 89: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 4

5.2 Bracketing roots

Many of the useful algorithms for finding roots depends on the ability of bracketing

a root as a prerequisite. This means that before these methods are going to work, we

need to know an interval in which a root lies, and in which there is no other root. As

an example let us look at the cubic equation

x3 − 6x2 + 5x + 7 = 0

Elementary theory of equations tell us that this equation has three roots, all of which

are real. We now want to pin these roots down to some suitably small intervals.

A knowledge of the nature of the graph makes this task a whole lot easier. One of the

best methods we have for this is, of course, the plotting of graphs. So, let us fire off

gnuplot :

gnuplot> set zeroaxis

gnuplot> f(x) = x**3-6*x**2+5*x+7

gnuplot> plot f(x)

In the first line we have asked gnuplot to plot the two axes too - this will make identifying

the points where the graph crosses the X axis simpler. Next, we have defined the

function f(x) - the syntax is very much like python’s, as you can see. Finally, the plot

command throws up the graph in figure(5.1a). As you can see, there seems to be three

zero-crossings within x = −5 and x = 5. To see this better, we can ask gnuplot to

confine the plot to −5 ≤ x ≤ 5

gnuplot> plot [-5:5] f(x)

this throws up figure(5.1b). Here, it should be clear that the three xero crossings are

in the intervals (−2, 0) , (2, 4) and (4, 5). We can pinpoint the roots down further by

narrowing the plot regions

Page 90: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 5

Figure 5.1: Bracketing the roots of the cubic x3 − 6x2 + 5x + 7 = 0.

gnuplot> plot [-2:0] f(x)

gnuplot> plot [2:4] f(x)

This throws up figures(5.1c) and (5.1d) - clearly indicating that the two smaller roots are

in the intervals (−1,−0.5) and (2, 2.5)respectively - we can narrow this down consider-

ably by making the intervals smaller - but we have already achieved what we need - three

intervals in each of which there is one and only one root - namely, (−1,−0.5),(2, 2.5)

and (4, 5). So, we have managed to successfully bracket the roots graphically.

If you are using the newer versions of gnuplot (version 4.0 or higher), then the job is

even easier. In these newer version, clicking the mouse at a point in the graph tells you

the coordinates of that point - so clicking on the points where the graph crosses the X

axis immediately gives us a rough idea of where the roots are.

Although the above method is adequate for our purpose, it may make you think that

Page 91: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 6

using gnuplot to locate the roots is cheating - can’t we write a program that does the

job? Writing a program that automatically brackets the roots for a general function

is surprisingly tricky - but we can write reasonably simple programs to do the job for

continuous functions. A very important idea behind such programs is that

If a continuous function f (x) has opposite signs at x = a and x = b, then

there must be at least one real root of the equation f (x) = 0 between a and

b. The root is then bracketed in the interval (a, b).

In other words, if the graph of a continuous function is on opposite sides of the X

axis at two points, it must cross the axis at least once in between! Of course, if f (a)

and f (b) have the same sign, there is no guarantee that there are no roots in the the

interval (a, b) - all we can say is that if the graph crosses the X axis once in between,

it must cross back. Thus, there can only be an even number of roots between a and b.

Remember, zero is an even number!

5.3 The bisection method

Now that we have bracketed a root, there are several different ways in which we can

proceed to hunt it down. One very simple method is called the bisection method. In

this, we start with the initial bracketing interval (a, b) and bisect it at c = a+b2

. Now the

root must lie in either the interval (a, c) or (c, b). Comparing the sign of the function

f(x) at a, b and c will tell us which of the two intervals we want. We then keep on

repeating the procedure, moving c to either a or b as necessary. The only point of

concern left is - when to stop? For this,we may predefine a tolerance level ε and stop

when the size of f (x) at the midpoint falls below this size. Of course, if the user makes

a mistake in specifying the bracketing region this procedure can go on for ever. So, it

is useful to check first whether the interval really brackets a root - i.e. check at the

very outset whether the function does have opposite signs at the two points a and b.

Remember that even if this is OK, the method will fail if the function is discontinuous -

Page 92: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 7

so it is a good idea to keep track of the number of steps being carried out and terminate

the program if this exceeds some preset limit. A program using this method for solving

the cubic equation x3 − 6x2 + 5x + 7 = 0 could be:

def f(x):

return x**3-6*x**2+5*x+7

# bracketing interval

a = -1.0

b = -0.5

maxSteps=100

tolerance=1e-6

#checking whether the bracketing interval is OK

if f(a)*f(b)>0:

print ’Not a proper bracketing interval’

else:

i = 1

while i <= maxSteps:

c=(a+b)/2.0

if abs(f(c))<tolerance:

print ’Root reached in’,i,’bisections’

print ’Root is \t’,c

print ’f(x) at root is’,f(c)

break

else:

i = i + 1

if f(c)*f(a)<0:

# root is between a and c

b=c

else:

# root is between c and b

Page 93: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 8

a=c

else:

print ’The bisec-

tion method failed to reach a root in’,maxSteps,’ steps’

The program, when run, results in

$ python bisection.py

Root reached in 21 bisections

Root is -0.714478731155

f(x) at root is 1.99877375984e-07

In this program the while loop will be run maxSteps times (the variable i taking the

values 1,2,3,...,maxSteps), unless of course the value of f (c) comes sufficiently close

to c. In our example, this will happen in the 21st pass of the loop. This makes the

interpreter print the value of c as well as f (c). Moreover, this also makes the interpreter

meet the break statement. This stops the while loop and since in this program there

is no code to follow the loop, this also terminates the program. Since we do not want to

go on and on even after our goal has been reached, the break statement makes eminent

sense here!

A new concept that I have introduced in this program is the while ... else: con-

struct. In this case, the body of the else : statement is executed when the condition

of the while command fails. Note that in the above program, the break causes what

is called an abnormal termination - the condition of the while statement never fails (i

never gets a chance to exceed maxSteps) and hence the else : clause is never called.

Of course, for a particularly ill-behaved function it is possible that f (c) will not get

sufficiently close to zero even in maxSteps steps and this will mean that the loop will

end gracefully, the else : clause will be executed, printing, in this case, the message

that the bisection method failed to converge!

We will meet another loop - the for loop in a while. There, too, you can have the else

: clause. Once again, this will come to effect once the for loop ends naturally - if it

terminates abnormally via a break statement, the clause is not executed.

Page 94: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 9

5.4 Evaluating polynomials better

One important change that will make a program like this much more efficient is to

change the routine that evaluates the function f(x) to

def f(x):

li = [1,-6,5,7]

p = li[0]

for i in [1,2,3]:

p = p*x+li[i]

return p

In case you are wondering what this does, let’s just walk through this code. The very

first line in the body of the function definition introduces us to a new kind of python

type - the list. A list is an ordered set (very much like row matrices in mathematics).

In python you write a list by writing its elements seperatedby commas, inside square

brackets. So, in our function, the list li stores the four numbers 1,-6,5 and 7 in that

order. These, of course, are the coefficients of x3, x2, x1 and x0 in our polynomial. Once,

you have stored the entries in a list, you can access them by referring to the index of

a particular entry. This is done by writing the name of the list, followed immediately

(no interbening space) by the index, enclosed in a square bracket. The four elements

of our list has the indices 0,1,2 and 3. So, a python interactive session may look like

> > > li = [1,-6,5,7]

> > > li[0]

1

> > > li[1]

-6

> > > li[-1]

7

> > > len(li)

4

Page 95: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 10

As you may have guessed, len() is a function that returns the length of the list which

is given to it as an argument. It also takes no great deal of genius to guess that, in

python, negative list indices actually count from the end - so that li[-1] is actually

the last entry, li[-2] is the last but one and so on ... Python has a very elaborate set

of things that you can do with lists, and we will meet a few of them as we go on. Let

us now return to the function that we have just written down.

At first, the variable p stores the value li[0] (remember, the first entry in a list is

indexed 0, not 1) which in this case is 1. Next, I have introduced a new concept - the

for loop. To understand it, first note that it is a complex statement (as evidenced by

the ‘:’ at the end of the header line). What this does should be rather obvious - the

variable i takes each value in the list [1,2,3] in succession. For each value of i, the

block of statements in the for loop (as for all complex statements, just which lines in

a program are in the body of a for loop is decided by the indentation) runs once. So,

in our case, the loop runs three times, each time with a new value of i . Once the list

is exhausted, the loop exits! After the first run, the variable p becomes p*x - li[1] ,

which is 1 ∗ x− 6. On the next run, it becomes (1 ∗ x− 6) ∗ x + 5, whereas in the next

and final run through the loop, it becomes ((1 ∗ x− 6) ∗ x + 5) ∗ x + 7, which as you

can readily check, is the same as our polynomial!

Granted that this method produces the same result as the original function declaration -

the immediate question is - why would anyone want to work a simple polynomial out in

this convoluted a fashion? The answer lies in efficiency. Note that every time the loop

is traversed, there is one multiplication and one addition - so the overall evaluation

takes three additions and three multiplications. Compare this with the more direct

evaluation

x**3-6*x**2+5*x+7

note that even the evaluation of x3 alone takes three multiplications! The direct method

actually requires all of seven multiplications and three additions - a full four extra

multiplications! This may not look like a huge difference - but in this case remember that

21 bisections had to be carried out, and each step requires three function evaluations.

Page 96: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 11

So, this apparently tiny difference has a measurable effect. If you want to find out

whether this is really a serious difference, you can use the linux command time. In my

machine (AMD Athlon 2.4GHz, 256 Mb RAM), it gives

$ time python bisection.py

Root reached in 21 bisections

Root is -0.714478731155

f(x) at root is 1.99877375096e-07

real 0m0.058s

user 0m0.010s

sys 0m0.000s

Changing the function as mentioned above and calling the new program bisection2.py,

I get

$ time python bisection2.py

Root reached in 21 bisections

Root is -0.714478731155

f(x) at root is 1.99877375984e-07

real 0m0.035s

user 0m0.030s

sys 0m0.000s

So, on my machine, the difference is between 58 ms and 35 ms. Although the difference

is tiny in real time, the proportionate difference is considerable - changing the function

evaluation causes a 40% improvement! In more complicated programs this may mean

the difference between a useful program and one which is completely useless1!

1Allow me to quote the much used book Numerical Recipes by W. Press et al in this context

Page 97: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 12

Actually, the “improved” function evaluation routine that we wrote right now is a bit

of an overkill. Since our polynomial is a short one, it is actually a much better idea to

write

def f(x):

return ((x-6.)*x+5.)*x+7.

which does the same job with a lot less fuss. The more sophisticated version above is

necessary when we want to evaluate polynomials of a very high order.

5.5 The regula falsi method

The bisection method has the advantage of being sureshot. What this means that once

we have bracketed a root, this method is sure to keep the root bracketed inside an ever-

shrinking interval and we can get as close as we want to the actual root by carrying on

more and more bisections.

One difficulty that this method has, though, is that it always bisects the current interval,

regardless of the values of f (a) and f (b). To illustrate why this may be a difficulty, let

us consider a function that has a root at 1.50001. If the bracketing interval is a = 1.0

and b = 2.0, the first bisection will take you to c = 1.5, which is very close to the root

indeed! If f (1.5) is smaller than the tolerance, this is where our bisection ends - fair

enough! If not, though, the next bisection takes you to 1.75 - quiet far away! It takes

many more bisections to get the answer sufficiently close to the root after that - a very

inefficient state of affairs inded! An obvious remedy is to try to look for a method that

moves the next point only slightly if it has already got close to the root. The trouble

is that we do not know where the root is - after all, finding it is the point of the whole

We assume that you know enough never to evaluate a polynomial this way:p=c[0]+c[1]*x+c[2]*x*x+c[3]*x*x*x+c[4]*x*x*x*x;... Come the (computer) revolution, all persons found guilty of such criminal behaviorwill be summarily executed, and their programs won’t be!

Page 98: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 13

exercise - so, how do we know when we are close? One way to guess whether we are

close is to compare the sizes of f (a) and f (b) - the next guess should be closer to

whichever endpoint the function is smaller in size.

The method of false position or regula falsi as it is officially called takes this approach. In

this method, we don’t bisect the bracketing interval. What we do is that we approximate

the curve f (x) between the points (a, f (a)) and (b, f (b)) by a straight line. The straight

line joining these points is

y − f (a) =f (a)− f (b)

a− b(x− a)

so that the point where it cuts the X axis is given by

c =bf (a)− af (b)

f (a)− f (b)

This is the new estimate for the root (as opposed to a+b2

in the bisection method). If

the value of f (c) is sufficiently small we stop there, otherwise we decide on which of the

two intervals (a, c) and (c, b) contains the root, reassign either b or a to c accordingly

and repeat. This is shown graphically in figure (5.2).

To program the method of false position, all you have to do is to change the line

c = (a+b)/2.0

in the bisection.py program to

c = (b*f(a)-a*f(b))/(f(a)-f(b))

One trouble with this method is that it requires too many evaluations of the function

(four in the line above to calculate c as opposed to none for this purpose in the bisection

method!). A way out which is highly recommended is to store the values of f (a) and

f (b) in two variables fa and fb so that the relevant portion looks like

Page 99: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 14

Figure 5.2: The regula falsi method. We start with the root brackected between thepoints marked 1 and 2. The straight line joining these points gives us the new endpointmrked 3. Since in this case, the root is between 1 and 3, we continue with that intervalgetting the point 4 and so on ...

Page 100: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 15

fa = f(a)

fb = f(b)

c = (b*fa-a*fb)/(fa-fb)

Using the regula falsi method program to solve our cubic equation gives us

$ python regulafalsi.py

Root reached in 8 bisections

Root is -0.714478714413

f(x) at root is 4.52771294235e-07

which shows an improvement over the bisection method in that it requires a smaller

number of iterations. Such an improvement is typical, but there can be pathological

functions where the regula falsi method actually fares worse - see figure(5.3).

The two methods that we have discussed so far by no means exhaust all available

methods for root finding. For example, the secant method is very much like regula falsi,

but rather than choosing that interval that brackets the root, we always work with the

last two function evaluations. This is actually a good idea in some cases. For example,

in the case shown in figure (5.2), the insistence on keeping the root bracketed leads us

to use the value of the point marked 1 over quite a lot of steps, despite the fact that

some of the intermediate points that we discarded were actually far cloaser to the root

than the point marked 1.

There are many more methods to find roots, too many to discuss here. One method that

we will discuss, though, is perhaps the best simple method there is to find roots in the

case of functions where the derivative is available to us. This is the Newton-Raphson

method, which we will take up in the next chapter. First, though, we will discuss some

very important issues involving the process of iterations - doing something over and

over again.

Page 101: Programming for Physics in Python

CHAPTER 5. ROOT FINDING 16

Figure 5.3: An example of a function where regula falsi method will take a long timeto converge to the root. The bisection method will actually work better in this case!

Page 102: Programming for Physics in Python

Chapter 6

The power of iterations

Iterations, i.e. repetitions are what computers are really good at. To see how iterating

can be useful let us examine an example of doing a well known calculation in a slightly

different way. Consider the following rule :

xn+1 =xn

2+

1

xn

(6.1)

which allows you to calculate the next, n + 1th term of a sequence from the nth term.

To see how this sequence evolves, let us start from x0 = 1. Then,

x1 =1

2+

1

1=

3

2

x2 =1.5

2+

1

1.5= 1.41667

x3 = . . .

and so on.

As you can see, the successive numbers are getting harder to calculate. To get the

iterates with very littel fuss, all you have to do is - of course, use python! This is so

simple that I will not even bother to write a program - just use the python interpreter!

> > > def iter(x):

1

Page 103: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 2

... return x/2.+1/x

...

> > > x=1

> > > for i in [0,1,2,3,4]:

... x=iter(x)

... print i+1,’\t’,x

...

1 1.5

2 1.41666666667

3 1.41421568627

4 1.41421356237

5 1.41421356237

First of all I have defined a function iter which calculates the right hand side of the

expression in (6.1). Then we have the for loop, which in this case will run 5 times. Of

course, what we do in the body of our loop is calculate the next value of x using the

function iter and print out its value, along with the current value of the loop index i.

We will return to the for loop soon - but for the time being, let us take a look at

the iterates that our loop prints out. The numbers 1.5, 1.41666666667, 1.41421568627,

1.41421356237, 1.41421356237 are clearly getting closer and closer to a well known

number - the square root of 2! To check whether this suspicion makes sense, just note

that if you chose xn as√

2 itself, the next iterate xn+1 will be

√2

2+

1√2

=√

2 !

In other words√

2 is a fixed point of our iteration - if you start there - you will stay

there forever. So, if our iterates starting from arbitrary values do get closer and closer

to a number - that number has to be1√

2! Of course, there is a very big if in the above

statement - what guarantee do we have, ab initio, of the sequence at all converging to

1Actually it is very easy to find the fixed point of the iterations that we are looking at - all we haveto do is to realise that if x∗ is a fixed point, then the iterate that you will get starting from x∗ is also

Page 104: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 3

a fixed point? As we will see in a while, this is a very tricky issue in general! Indeed,

the behaviour of successive iterates of even simple functions have been found to be way

richer than what scientists had ever imagined! For our iteration, though, the behaviour

turns out to be rather straightforward. You may think that the fact that our iterations

converge to√

2 is because we started out with a x1 which was rather close to√

2 to

begin with, namely 1. what if we had started with x1 = 1000? Rather than go into

any deep theory as to why this must be so, let us just check this out. All we have to

do is to get python to do the same thing again, but this time starting at 1000. At this

point, you may realise that not writing the program down and working in the shell may

not have been a very good idea! If we had just stored the steps in a file called, maybe,

sqrt.py - all we would have to do now is just change one line! Here we will have to enter

the whole thing again2! So, lets just rectify things now - we will actually type out the

file and start all over again. The results this time are

1 500.001

2 250.002499996

3 125.005249958

4 62.510624643

5 31.2713096021

This doesn’t look so encouraging on first sight - the iterates are pretty far away from√2! However, the good thing is - they are coming down, so maybe if you went about it

a few more times we will come closer to the goal! We can change the line heading the

for loop into

the same. In other words, x∗ must satisfy

x∗ =x∗

2+

1x∗

which simplifies to (x∗)2 = 2. So,√

2 is deinitely one of the fixed points - but there is another one -namely, −

√2. Indeed, if we had started with a -ve number, we would have landed up at −

√2 after a

lot of iterations.2If you have been using the python shell that comes as part of IDLE - things would not be this

bad! First type the line x = 1000 . Now move the cursor anywhere inside the for block and hit enter- the entire block will bcomee available to you in the prompt - just edit the header line and you canrun the iterations again.

Page 105: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 4

for i in [0,1,2,3,4,5,6,7,8,9]:

and the result is

1 500.001

2 250.002499996

3 125.005249958

4 62.510624643

5 31.2713096021

6 15.6676329949

7 7.89764234786

8 4.07544124052

9 2.28309282439

10 1.57954875241

- we do seem to get closer! The only trouble is, now the for loop header seems to be

getting out of hand. Its all very well to say that a for loop with a header like

for i in list

the loop will run with i taking each successive value in the list, but when you want to

run a loop, say, a 100 times - will you have to go through the trouble of writing out a

list of numbers from 0 to 99? What’s worse, even if you did manage that, changing the

number of times you want to run the loop would mean that you have to rewrite the list

again. Python does give you a neat solution to these problems - this is the range()

function.

So, just what is this function, range() ? One advantage of having an interpreted

language is that you can always ask the interpreter!

> > > range(5)

[0,1,2,3,4]

Page 106: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 5

So the range() function returns a list - in this case a list containing the numbers 0,1,2,3

and 4! So, instead of for i in [0,1,2,3,4]:, we could just as well have written for

i in range(5):.

The actual numbers that make up the list may surprise you a bit, but if you type

help(range) at the python prompt, the mystery clears in no time -

Help on built-in func-

tion range: range(...)

range([start,] stop[, step]) -> list of inte-

gers Re-

turn a list containing an arithmetic progression of inte-

gers. range(i, j) returns [i, i+1, i+2, ..., j-

1]; start (!) defaults to 0. When step is given, it speci-

fies the increment (or decrement). For example, range(4) re-

turns [0, 1, 2, 3]. The end point is omitted! These are ex-

actly the valid indices for a list of 4 elements.

A few more examples may help :

> > > range(1,6)

[1,2,3,4,5]

> > > range(1,9,2)

[1,3,5,7]

Just why does the list stop short of the stop argument? Well - take another look at

range(5)! It has precisely 5 elements. Remember, in python list indices start with 0 -

so the elements in range(5) are preciely the indices of the successive elements in a 5

element list. You will soon see why this is a very useful feature when dealing with lists.

Note that the range() function returns a non-empty list only if you can go from the

start argument to the stop argument in steps of one (or the third argument - if there

is any) - otherwise it returns the empty list []. Just try out a few more variants at the

python shell :

Page 107: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 6

> > > range(1,5,-1)

[]

> > > range(5,1,-1)

[5,4,3,2]

and so on!

After spending so much time on lists of successive integers, let me hasten to add that

the list in the for loop header line can be any list - the iteration counter variable will

take the values of the list elements in succession. To check that you understand this,

see whether you can figure out what the following does:

days = [’Sun’ ,’Mon’ ,’Tue’ ,’Wed’ ,’Thu’ ,’Fri’ ,’Sat’]

orders = [’first’,’second’,’third’,’fourth’, ’fifth’, ’sixth’, ’sev-

enth’]

i = 0

for day in days:

print ’The ’,orders[i],’day of the week is ’,day

i=i+1

Coming back to the original iterations, we can easily modify our original program to

read

def iter(x):

return x/2. + 1./x

n=15

x=1000

for i in range(n):

x=iter(x)

print i+1,’\t’,x

Page 108: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 7

Storing this in a file sqrt.py and then typing

$ python sqrt.py

gives the output

1 500.001

2 250.002499996

3 125.005249958

4 62.510624643

5 31.2713096021

6 15.6676329949

7 7.89764234786

8 4.07544124052

9 2.28309282439

10 1.57954875241

11 1.42286657958

12 1.41423987359

13 1.41421356262

14 1.41421356237

15 1.41421356237

which does show a rapid convergence towards√

2! Indeed, it is pretty easy to understand

why this works even with a large initial value x0. After all, if xn is large, we can easily

ignore the term 1x

in comparison with the term x2

- and so, initially, the iterates keep on

(roughly) halving - so that in no time at all, x is forced close to unity - and successive

iterates do take it to the fixed point. You may of course try experimenting with the

values of n and x0 to see how the successsive iterates come out.

Now that we are reasonably certain that no matter where we start - the iterates will

converge to√

2, we can even use it to calculate the value of√

2! In this case, though

the trouble is that to get to exactly√

2 the computer should need an infinite number of

Page 109: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 8

loops (assuming it could calculate to as many places of decimal as you wish and it had

unlimited memory space). No matter how fast your computer is - an infinite number of

loops still will take an infinite time to execute! The answer of course is not to try to get√2 exactly - but get as close to

√2 as you need. However, in this case we have another

problem - how can we know beforehand how many iterations we must carry out? The

answer is, we can not - and so we must fall back upon another kind of loop - the good

old while loop!

You might try the following :

def iter(x):

return x/2.+1./x

x = 1000

i = 1

while 1 :

x = iter(x)

print i,x

i = i+1

in this case, the while loop has 1 as the ‘condition’ - this actually means that the

condition is always true (in python any nonzero value is taken to be true) and the loop

will run forever. Of course, this is not very desirable - no one wants to see an endless

string of numbers scrolling down the screen! So, what we need is some means by which

to know when to break the loop. Since we know (or at least expect) that the iterations

will converge to√

2, we can try to check whether the iterate has got sufficiently close

to this value and break the loop if it has. the command that allows you to break a

running loop is called, understandably, break! When the interpreter encounters the

break statement, it stops whatever loop that is running at the time and returns control

to the next higher level. With this modification, we can rewrite the program as

from math import *

def iter(x):

Page 110: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 9

return x/2.+1./x

x = 1000

i = 1

closeEnough = 1e-06

while 1 :

x = iter(x)

print i,x

if abs(x-sqrt(2)) < closeEnough:

break

i = i+1

Running this program will result in

1 500.001

2 250.002499996

3 125.005249958

4 62.510624643

5 31.2713096021

6 15.6676329949

7 7.89764234786

8 4.07544124052

9 2.28309282439

10 1.57954875241

11 1.42286657958

12 1.41423987359

13 1.41421356262

As you can see, the iteration ends when the value of xn reaches a value sufficiently

close to√

2, which according to the math.sqrt() function is 1.4142135623730951. Can

you see why the condition checking closeness in the if statement has abs(x-sqrt(2))

instead of plain (x-sqrt(2)) ?

Page 111: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 10

One objection that can be immediately raised about this program is that it is all very

well to compare the value of xn with√

2 in this case, since here we know the fixed point

in advance and have the means of calculating it with sufficient accuracy without relying

on this program itself (namely, the math.sqrt(2) function call)! How can we figure out

when to stop when we don’t know the value of the fixed point in advance? One way to

handle this sort of problem is to stop when two succesive iterates come out sufficiently

close to each other. With this approach, our program will become :

from math import *

def iter(x):

return x/2.+1./x

x = 1000

i = 1

closeEnough = 1e-06

while 1 :

xn = iter(x)

print i,xn

if abs(x-xn) < closeEnough:

break

i = i+1

x = xn

As you can see, here we have compared between the previous iterate, stored in the

variable x and the new one, stored in xn. Note that we had to modify the iteration line

to

xn = iter(x)

in order to avoid overwriting the previous value of x. Also, note that at the end of

the loop, which is reached only if the iterates are not sufficiently close, we change the

value in the variable x to the current iterate so that the next loop can proceed correctly.

Page 112: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 11

Run this program and see what difference, if any, our modifications make for various

different starting values x0.

Let’s address one final point before we move on to the next problem. In this particular

problem we were reasonably sure that the successive iterates do converge to a fixed

point. As we will see later, this is by no means guaranteed for any iteration - so

there’s every chance that our iterates will keep on going for ever if we happen to be

unlucky. What we may do is to break the loop, irrespective of whether we are cllose

enough to the fixed point or not, if a prechosen (maybe large) number of iterations are

exceeded. So, we ask the interpreter to execute the break statement either if abs(x-xn)

< closeEnough, or if i > 1000 (the number 1000 here is just for illustrative purposes

- you can even choose it to be a variable - which you must, of course, assign before the

checking line is reached) - so the if statement becomes

if abs(x-xn) < closeEnough or i > 1000 :

break

In this example, this modification will have no effect (unless of course, you choose to

start from a very high value of x0) but it can be a lifesaver for other iterations.

You may feel that I owe you an explanation on an important issue - just how did I

manage to hit upon the iteration formula (6.1)? Of course, this is not something I

dreamt up one fine morning - I will reveal its origins a little later. But now, we will

take a look at a different iteration - not much different from the one that we have been

looking at so far - but with unexpected riches hidden in an apparently simple exterior.

6.1 The rise and fall of fish populations

You may feel slightly ill at ease with the title of this section. After all, since when

have fish had anything to do with physics? And what, prey, can their connection with

programming be? Well, I am using this example more for historical reasons - since it

has been one of the starting points of one of the most fruitful branches of present day

Page 113: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 12

physics and mathematics - so bear with me for a while and we will see what this can

lead us to!

In the early 1960s, the Australian mathematician Thomas May entered the then almost

untapped field of mathematical biology with an attempt to understand the variations

of fish population in a particular river. Local lore had it that if the fish were plentiful

one year, they were sure to be in short supply the next, and vice versa! In other words,

the fish population showed a biennial cycle3. May was interested in seeing whether he

could use mathematics as a tool to understand this.

One model of population growth that has been around for a long long time holds that

populations (whether human or fish) tend to grow, or fall, exponentially - at least as a

first approximation. If the population in the nth year is pn- then during the year its rise

is proportional to this number. If the growth rate is given by γ (this is the difference

between the birth rate and the death rate), the population in the next year is given by

pn+1 = pn + γpn = λpn

where λ is, of course, 1 + γ. In this model, it is easy to see that in this case, we have

pn = λnp0

and so the population will keep on growing if λ > 1 - which means that birth outstrips

death. On the other hand the population will dwindle away if λ < 1. Of course, this

model can not explain the biennial cycle of the fish population mentioned above.

May found that a very simple modification of the above model will do the trick. After

all, he reasoned, the growth rate can not stay constant indefinitely. The larger the

population, the harder will it be to sustain - so the growth rate should actually decrease

with rising population. The simplest way in which we can take this into account is to

3Nearer home, you may know that the mango production in the district of Malda shows the samesort of variation.

Page 114: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 13

claim that the growth rate in the nth year is γ − βpn so that the equation becomes

pn+1 = λpn − βp2n

As you can see, this is just about the simplest variation that one can think about of the

original model - instead of a linear function, all we have done is take the next simplest

possibility - a quadratic one! This may make you rather sceptical as to the extent to

which this modification will be useful - but just you wait and see!

Before we begin to explore the consequences of our modified growth law, it may be a

good idea to rewrite this in a more convenient form. As you can see, a population larger

than p = λβ

will lead to a negative population the next year - so p is the upper limit

that the population can reach. We will introduce a new variable - the scaled population

x = pp

= βλp so that the growth law becomes

λ

βxn+1 = λ

λ

βxn − β

βxn

)2

which reduces to

xn+1 = λxn (1− xn) (6.2)

This equation is called the logistic equation and is one of the central equations in the

modern theory of chaos.

One final point - xn must be between 0 and 1. Since the largest possible value that

x (1− x) takes in the interval [0, 1] is 14

(Check this!) we must keep λ within 0 and 4

to ensure that the iteration does not take x out of its allowed range.

6.2 The iterates of the logistic map

Let’s start with the case of small λ - the case where λ < 1. In the linear case, this

corresponds to the situation where the population (and hence x) dwindles away to zero.

Since for small values of x the quadratic x2 term becomes really small, in this case we

Page 115: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 14

should not expect anything new.

Indeed, if we try to find out the fixed point(s) x∗ of this iteration we find

x∗ = λx∗ (1− x∗)

which gives us two choices

x∗ = 0, or x∗ = 1− 1

λ

So, the zero population case is a fixed point odf our iteration. The other one, 1 − 1λ

is negative when λ < 1 - and so of course is not relevant here. However, the fact that

x∗ = 0 is a fixed point just tells us that if you start at 0, you will stay there - what we

are interested in exploring is whether the iterates will converge to this fixed point in

the long run if you start somewhere else.

By now, it should be a cinch to write a program that helps you to explore this question.

Since we want to explore the behaviour of the iterations in the long run - we can just

throw away the first iterations (where we are likely to encounter transient effects) and

just make our pogram print out a few iterates after that. A possible program would be

from random import random

def iter(x):

return l*x*(1-x)

l=input(’Enter the value of lambda : ’)

x=random()

for i in range(1000):

x=iter(x)

for i in range(10):

x=iter(x)

print x

Page 116: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 15

This program, of course, carries out the first 1000 iterations without showing them on

the screen and then prints out the next 10 iterates. As you may have guessed, the

random module provides python with several functions for generating random numbers

- the random() function, in particular returns a random number between zero and one.

Staring with λ = 0.9 gives, as expected,

$ python logistic.py

Enter the value of lambda : 0.9

1.13933524513e-47

1.02540172061e-47

9.22861548552e-48

8.30575393697e-48

7.47517854327e-48

6.72766068894e-48

6.05489462005e-48

5.44940515804e-48

4.90446464224e-48

4.41401817802e-48

3.97261636021e-48

So, the iterates are converging to the fixed point x∗ = 0. What if we start with a value

larger than 1 for λ? With the program at hand, this is easy to check out :

$ python logistic.py

Enter the value of lambda : 1.7

0.411764705882

0.411764705882

0.411764705882

0.411764705882

0.411764705882

0.411764705882

Page 117: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 16

0.411764705882

0.411764705882

0.411764705882

0.411764705882

This time, the iteration has converged, but not to zero. It has actually converged to

the other fixed point 1− 1λ

as you can easily check.

The way in which the iterates approach the fixed point is an interesting topic in itself.

There is a neat graphical trick that you can use to visualize this. Draw the graph of

λx (1− x) vs. x which, of course, is a parabola. If you mark off x0 on the X axis then

x1 can be read off from the point where the vertical line through this point intersects

the curve. To repeat the iteration, we have to mark off x1 on the x axis again, and

repeat the whole procedure over and over again. A short cut way of doing the same is

- just draw the y = x straight line too. From the point where the vertical line through

(x0, 0) intersects the parabola draw a horizontal line till it cuts the y = x line, then

move vertically till you cut the parabola again, then horizontally, then vertically and so

on ... Convince yourself that this procedure is actually going to generate the successive

iterates correctly. These diagrams are called cobweb diagrams - since at least for some

combinations of λ and x0 they look uncannily like a spider’s web. It is rather easy to

write a program that will generate the cobweb diagram. Note that we need to start

from the point (x0, 0) - move vertically to the point (x0, x1), then horizontally to the

point (x1,x1), then to (x1, x2) and so on. The program cobweb.py that that will do the

trick is

from random import random

def iter(x):

return l*x*(1-x)

f = open(’cobweb.out’,’w’)

Page 118: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 17

x=random()

l=input("Enter the value of lambda : ")

print > > f, x,0,’\n’,x,

for i in range(100):

x = iter(x)

print > > f , x, ’\n’, x, x, ’\n’, x,

print > > f, iter(x)

Note the ‘,’ at the end of the print statements - this prevents the next item to be printed

from going into the next line. We may fire up gnuplot and use this program as follows :

gnuplot > f(x)=l*x*(1-x)

gnuplot > !python cobweb.py

Enter the value of lambda : 2.9

!

gnuplot> l=2.9

gnuplot> plot [0:1] f(x), x, ’cobweb.out’ w l

Note the use of the ‘!’ character at the gnuplot prompt to pass the command directly

to the shell. You can of course run the program cobweb.py directly from the command

line and run gnuplot later. Running the program from the gnuplot prompt can make

life easier if you want to run the program for several different values of λ - all you have

to do is to change the value you assign to l, and use the cursor ‘up’ key to run the plot

command again.

Figure (6.1a) shows the cobweb diagram for λ = 2.9. As for the case λ = 1.7 the

iterations quietly settle down to the nonzero fixed point x∗ = 1 − 1λ. Things change

drastically though, when you choose a value of λ larger than 3 :

$ python logistic.py

Enter the value of lambda : 3.1

Page 119: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 18

Figure 6.1: Cobweb diagrams for various different values of λ. (a) λ = 2.9 - note theconvergence to the fixed point at 1 − 1

2.9, (b) λ = 3.1 - note the period two cycle, (c)

λ = 3.6 - we already have the onset of chaos and (d) λ = 4 - full onset of chaos!

0.764566519959

0.558014125203

0.764566519959

0.558014125203

0.764566519959

0.558014125203

0.764566519959

0.558014125203

0.764566519959

0.558014125203

Something surprising has happened here - the iterations do not converge to either of the

Page 120: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 19

fixed points. What has happened is that the iterations have settled down to nice period

two cycles - rather like the fish population! Figure (6.1b) shows that even iterations

that start pretty close to the fixed point (where the straight line cuts the parabola)

actually move away sharply from it and settle down to the period two cycle. Instead of

a fixed point, the iterations now approach a limit cycle.

What happens for a larger value of λ? Check out λ = 3.5 :

$ python logistic.py

Enter the value of lambda : 3.5

0.826940706591

0.500884210307

0.874997263602

0.382819683017

0.826940706591

0.500884210307

0.874997263602

0.382819683017

0.826940706591

0.500884210307

- its a cycle once again - but this time with a period of 4!

Figure (6.1c) shows the cobweb diagram for λ = 3.6 - there is no sign of periodicity.

Nice periodic behaviour has given way to a chaotic one. Actually, a lot of things happen

in the narrow interval between 3.5 and 3.6,

One more example - we try λ = 4 but only after changing the logistic.py program so

that it prints out 20 iterates rether than 10.

$ python logistic.py

Enter the value of lambda : 4

0.132028596978

Page 121: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 20

0.458388186232

0.99307382782

0.0275128012768

0.107023388171

0.382277530221

0.944565680437

0.209445423111

0.662312151395

0.894619062038

0.377103183505

0.939585489982

0.22705838799

0.702011505733

0.836765406206

0.546356244733

0.991404394297

0.0340868850621

0.131699877316

0.457420078523

Look very hard at the above - you will find no hint of any periodicity anywhere! Also,

the iterates are scattered all over the [0, 1] interval as can be seen directly from the

cobweb diagram in figure (6.1d). What you have just seen in the last two cases is

an example of deterministic chaos - very complex behaviour emerging out of simple

deterministic rules!

The change in the nature of the iteration as λ changes is quite an important topic in

itself. This change in behaviour from one fixed point to another fixed point, to a period

2 cycle, then to a period 4 cycle, and so on till chaos is called bifurcation. We can

easily write a program that will show the behaviour of the iterates as λchanges. All we

have to do is add an outer loop to the previous program in which we change the value

of lambda in small steps and print both the value of lambda and the final few (maybe

Page 122: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 21

fifty) iterates to a file. A program to do this could look like

from random import random

def logistic(x):

return l*x*(1-x)

f = open(’bifurc.out’,’w’)

li=input(’Enter the starting value of lambda’)

lf=input(’Enter the final value of lambda’)

Numl=500

deltal=(lf-li)/Numl

l=li

while (l<lf):

x=random()

for i in range(1000):

x=logistic(x)

for i in range(50):

x=logistic(x)

print > > f, l,x

l=l+deltal

Plotting the output of this program with various starting and ending values of λ in the

range [0, 4] clearly shows the rich behaviour of the logistic map. Figure (6.2) shows

some illustrative results - you can of course generate many more for yourself.

Of course, there is a lot more to the long term behaviour of iterations than just a few

pretty (I hope) pictures. I have given you a first glimpse of the rich subject of nonlinear

dynamics and chaos - if you are interested, there is plenty of nice material around to

read further from.

Page 123: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 22

Figure 6.2: Details of the bifurcation diagram of the logistic map

6.3 Solving equations by iteration - the Newton-

Raphson method

Now that I have hopefully convinced you that even simple iterations can have extremely

rich behaviour, let us turn back to our very first iteration (6.1). Given the equation

governing the iteration, it is easy to see that the fixed point is√

2 - but just how did

we arrive at it in the first place? Actually this is just an application of an iteration

method for solving a general equation f (x) = 0 that was originated by none other than

the great Isaac Newton and subsequently refined by Raphson.

To understand the Newton-Raphson iteration scheme, consider the behaviour of the

function f (x) at a point ξ which is close to an actual solution of the equation. If

f (ξ) 6= 0, we are not at the desired solution - but we can get closer to the solution

Page 124: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 23

by approximating the function in the neighbourhood of this point by the tangent at

this point (all we are saying here is that a small enough piece of all smooth curves look

straight). Since the tangent has the slope f ′ (ξ), it intersects the X axis at the point

with ordinate x given by

f ′ (ξ) =f (ξ)− 0

ξ − x

so that

x = ξ − f (ξ)

f ′ (ξ)

If the curve had really been a straight line, the point x would really have been the

solution that we are looking for4. Of course, things are not so simple in general, but we

can at least expect that x is a better approximation to the root than ξ was. to improve

on this, we can repeat the whole process with x in place of ξ. This leads to the iteration

scheme

xn+1 = xn −f (xn)

f ′ (xn)(6.3)

which will hopefully converge to the actual root provided we start close enough to it.

It can be easily seen any fixed point of this iteration x∗ has to satisfy the equation

f (x∗) = 0 - so if the iteration converges, it must converge to a root. Of course, the

previous discussion on the logistic map should make you wary of whether the iterates

at all converge to a fixed point - and this may indeed be a concern if you your iterations

from a point which is not very close to the root. Despite this, the Newton-Raphson

method remains a very potent tool for solving equations.

Let us try to use the Newton-Raphson method to find the roots of the cubic equation

that we had been looking at in the previous chapter. With

f (x) = x3 − 6x2 + 5x + 7

4Another way to look at this is by the means of the Taylor expansion

f (ξ + δ) = f (ξ) + f ′ (ξ) δ +12f ′′ (ξ) δ2 + . . .

If you ignore quadratic or higher order terms in δ, the equation f (ξ + δ) = 0 is satisfied by δ ≈ − f(ξ)f ′(ξ) .

Page 125: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 24

we have

f ′ (x) = 3x2 − 12x + 5

and thus the NR iteration scheme becomes

xn+1 = xn −x3

n − 6x2n + 5xn + 7

3x2n − 12xn + 5

=2x3

n − 6x2n − 7

3x2n − 12xn + 5

Let us try this scheme out by writing a program.

def NRiter(x):

num = 2*x**3-6*x**2-7.

den = 3*x**2-12*x+5.

return num/den

closeEnough = 1e-06

x = 1

maxSteps = 100

for i in range(maxSteps):

xn = NRiter(x)

if abs(x-xn) <= closeEnough:

print ’The NR iteration converged in’, i+1, ’ steps.’

print ’The root is \t’,xn

break

if i == maxSteps-1:

print ’The iterations failed to con-

verge in ’ ,maxSteps,’ steps’

Running this program produces the output

$ python NRiter.py

Page 126: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 25

The NRiteration converged in 5 steps.

The root is 2.14327732184

This is one of the roots of the cubic equation that we have been trying out. Try running

the program starting with an initial choice x = -1 and the iteration will converge to

the negative root -0.714478744388 that we had found before. For the third root, just

try starting at x = 4 and you will find 4.57120142255 - which as you can check, is the

correct root to the required order of accuracy.

You may be wondering exactly how we can be sure to find all the roots using this

procedure! After all, how can we at all be sure that starting from three different initial

guesses will converge to three distinct roots? Since there are an infinite number of

starting possibilities, while there are only a finite number of roots (3 in this case), it

is obvious that unless we are very careful in chosing the starting point, we will keep

on landing up at the same root over and over again! There are special tricks that can

help us to home in on the roots, but we will not go into the details here. If you are

interested, by all means look up standard books on numerical methods.

As long as we are using the Newton Raphson method for solving a polynomial equation,

there is a nice trick that can help you to define the iteration function. This is based

on the following nice code that not only computes a polynomial rather efficiently, but

manages to calculate its derivative at the same time :

li = [1,-6,5,7]

p = li[0]

dp = 0.0

for i in li[1:]:

dp = dp*x + p

p = p*x + i

The new construction that you see above is slicing a list - a very powerful technique

that makes manipulation of lists a joy in python. To see what it does, just use the

python interpreter and type

Page 127: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 26

> > > li = [1,-6,5,7]

> > > li[1:]

[-6,5,7]

> > > li[:3]

[1,-6,5]

> > > li[1:3]

[-6,5]

> > > li[:]

[1,-6,5,7]

> > > li[3:2]

[]

This should give you a fair idea of how list slicing works. To slice a list, follow the

name of the list by square brackets, with two integers seperated by a colon. The first

integer gives us the index of the item that the slice starts with, while the second integer

is the index of the item before which the slice stops - the slice includes all elements of

the original list in between. The default value of the first integer is 0, while that of

the last one is len(li) - the length of the list. So, li[1:] essentially gives us a new

list, which has all elements of the list li except the first! Of course, if you leave both

the integers out, you get the whole list! So, li[:] is actually a new list which is an

exact copy of the list li! Can you see that the rules of list slicing actually mean that

li[:n ]+li[n :] always returns the original list. Negative slice indices are also allowed

and as like ordinary indices for lists, they count from the end of the list. As we have

already seen, if the numbers given as slice indices are such that there is no element

between the first index and the second index, then the slice returned is an empty list.

Now that I have told you what that li[1:] thing that is sitting in the for loop header

line is, it should be easy for us to verify that at the end of the loop the variable p gets

the value of all our polynomial and dp the value of its derivative.

Using this above piece of code you can easily generalize the NRiter(x) function above

to NRiterpoly(x,li) that defines the NR iteration for a polynomial. If you store the

Page 128: Programming for Physics in Python

CHAPTER 6. THE POWER OF ITERATIONS 27

coefficients of your polynomial in the order of descending powers of x in the list li, the

function definition can be

def NRiterpoly(x,li):

p = li[0]

dp = 0.0

for i in li[1:]:

dp = dp*x + p

p = p*x + i

return x-p/dp

You can try playing around with various polynomial equations and starting guesses to

figure out whether you can locate all the real roots or not.

Page 129: Programming for Physics in Python

Chapter 7

Reading between the lines -

interpolation

As often happens in experiments, you may know that one physical quantity y depends

on another x - but instead of knowing the form of the function y = f(x) that relates the

two, you may just know the values of y at certain values of x. A common question that

one often faces is, given the set of data points (x0, y0) , (x1, y1) , (x2, y2) . . . , (xn−1, yn−1)

- can we figure out what the value of y will be at other values of x? When the value

of x lies somewhere in between the extreme bounds of the values of x at which y is

known, this process is called interpolation. Sometimes we may want to find out the

value y will take for a x value beyond the bounds of the data set - this is what is called

extrapolation. Although extrapolating from a known set of data points can be quite

essential at times, this is always a more tricky process. Here we will keep ourselves

confined to the safer process of interpolation.

That even interpolation can be quiet fraught with errors can be seen graphically in figure

(7.1). Here, the black circles stand for the known data points. Obviously, an infinite

number of curves can be drawn going through each of these points - the figure shows only

two. Of course, there is no reason why a particular function cannot set out at an entirely

unexpexcted directions between two points where its values are known. All interpolation

schemes have to assume that the actual function is smooth and “reasonable”, however!

1

Page 130: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 2

Figure 7.1: The non-unique nature of interpolating curves - the black circles representdata points, an infinite number of curves can be drawn between them!

One approach to interpolation is to assume that the function is actually a polynomial

in x. Sinec a polynomial of degree d has d + 1 coefficients that can be adjusted, the

lowest order polynomial that is guaranteed to go through the n data points is of degree

n−1. In the next few sections we will discuss methods for generating these “co-location

polynomials”.

Before we proceed, a word of warning. do not confuse interpolation with fitting a given

function (very often a low-degree polynnomial) to the data points. Typically a fit is

carried out when you have some reason to suspect that the proposed function actually

describes the data. Since the number of parameters that you can tune in such cases

is less than the number of points, it is very likely that the function will not actually

go through all the points in your data set - indeed, it is quiet often the case that the

function goes through no data point at all! In this case, our aim is to fine tune whatever

adjustable parameters our expected function has in order to represent the data with as

little error as possible. As far as interpolation is concerned, we are free to choose the

interpolating function, so we can of course ensure that the chosen function goes through

all the data points. Note though that if physics of the tells us to expect a function with

Page 131: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 3

less freedom, fitting is by far the better option than interpolation!

7.1 Newton interpolation

A very simple method for interpolation can be used when the data points are sampled

at equal intervals of x. In other words, the values of x0, x1, . . . , xn−1 are in A.P. In this

case, we can define the new variable x by

x =x− x1

d

where d is the common difference. Then, the independent variables x1, x2, . . . xn take

the values 0, 1, . . . , n− 1. This standard set of values actually makes calculations a lot

easier. Of course, the original variable can be recovered from x by using

x = x1 + xd

In the following, we will use the x instead of the original variables - so that our data

points will be always at (i, yi), with i taking the n values 0, 1, 2, . . . n− 1. Our aim, of

course, is to find the unique n − 1 order polynomial that passes through these n data

points.

To understand the process of Newton interpolation, we will first introduce the difference

operator ∆ by the following definition

∆yi = yi+1 − yi

Compare this with the derivative operator D

Dy(x) =dy

dx= lim

h→0

y (x + h)− y (x)

h

you should see the difference operator is very similar - all you do is take h to be the

fixed value 1, instead of an infinitesimal for which you will take the limit as the quantity

Page 132: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 4

i yi ∆yi ∆2yi ∆3yi ∆4yi ∆5yi

0 12 -4 +1 +6 -15 +201 8 -3 +7 -9 +52 5 +4 -2 -4 -3 9 +2 -6 - -4 11 -4 - - -5 7 - - - -

Table 7.1: An example of a difference table.

tends to zero! So, the difference operator ∆ can be thought of as the discrete version

of the derivative operator1.

Just like you get the second derivative operator D2 by applying the operator D twice,

we can define the second difference operator

∆2yi = ∆ (yi+1 − yi)

= ∆yi+1 −∆yi

= (yi+2 − yi+1)− (yi+1 − yi)

= yi+2 − 2yi+1 + yi

Similarly, higher order difference operators can be defined too!

As you can of course see, given a set of n values y0, y1, . . . , yn−1 we can calculate n− 1

first differences ∆y0, ∆y1, . . . , ∆yn−1. Applying the difference opeator once again, we

can get n−2 second differences ∆2y0, ∆2y1, . . . , ∆

2yn−2. This can be continued over and

over, at most n− 1 times overall when we land up with the single n− 1 order difference

∆n−1y0. We can try writing this in the form of a difference table. An example of a

differnce table is shown in table (7.1).

If we have all the derivatives of a function at a point, we can use them to construct

1If this seems odd to you, welcome to the club! After all, the difference operator is way moreelementary compared to the derivative operator - it just talks of subtraction, instead of complicatedthings such as taking limits. It is only that we are more used to seeing the difference operator - andso we think of the difference operator as a “version” of it!

Page 133: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 5

the Taylor series expansion for the function - a power series which converges to it. A

very similar thing happens for a differences in the case of a polynomial. If we know

all of n − 1 order differences of a polynomial at a point, it can be used to reconstruct

the original polynomial. To understand how this can be done, let us first define the

factorial polynomial of degree m by

x(m) ≡ x (x− 1) (x− 2) . . . (x−m + 1)

We define the zeroth degree factoral polynomial x(0) to be identically equal to 1. The

name factorial polynomial is of course a reference to the fact that

m(m) = m!

It is quiet simple to understand that any polymial of degree m can be written as a

combination of factorial polynomials of degree m or less. So, we can write the n − 1

order polynomial that we are seeking in the form

f (x) =n−1∑m=0

amx(m)

and our job here is to find the n coefficients a0, a1, . . . , an−1.

What makes finding these coefficients very easy is that

• The factorial polynomials satisfy the formula

∆x(m) = (x + 1)(m) − x(m)

= (x + 1) x (x− 1) . . . (x−m + 2)

−x (x− 1) (x− 2) . . . (x−m + 1)

= {(x + 1)− (x−m + 1)}x (x− 1) (x− 2) . . . (x−m + 2)

= mx(m−1)

Page 134: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 6

and hence we have

∆lx(m) = m (m− 1) . . . (m− l + 1) x(m−l) for l ≤ m

and this vanishes identically for l > m.

• The factorial polynomial x(m) vanishes at all the m values 0, 1, 2, . . . ,m− 1.

Using these two properties it is very easy to see that the coefficients am that we are

seeking are given by

am =1

m!∆my0

So for example, the polynomial of degree 5 that fits the 6 data points described in table

(7.1) is given by

f (x) = 12x(0) − 4x(1) +1

2!x(2) +

6

3!x(3) − 15

4!x(4) +

20

5!x(5)

Very often, though, we are not interested in the actual functional form of the polynomial

- but rather in the value it takes for a particular value of x. We can easily write a

program to calculate this value. One point should be stressed, though. Calculating

the values of all the factorial polynomials will be rather a waste, since each can be

calculated from the previous one by a simple multiplication. The same, of course, goes

for the factorials in the denominator. A program that takes this into account is the

following:

def difference(l):

li = l[:]

di=[li[0]]

for i in range(len(li)-1):

for j in range(len(li)-i-1):

li[j]=li[j+1]-li[j]

di.append(li[0])

Page 135: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 7

return di

def newton(x,li):

fp = 1.

di = difference(li)

sum = di[0]

for i in range(1,len(di)):

fp = fp*(x-i+1.)/i

sum = sum + di[i]*fp

return sum

li = [12,8,5,9,11,7]

x = input(’Enter the value of x : ’)

print x, newton(x,li)

We will have to spend a lot of time modifying and accessing lists in the future, for the

time being note that the method that python provides for appending an item to a list

is called, obviously, append :

> > > l = [10,12]

> > > l.append(’13’)

> > > print l

[10,12,’13’]

In the function difference() above the list di starts out with just a single entry - the

first elements of the list li. Everytime one complete pass of the inner loop, the one

indexed by j, is completed, we append the first element of li to di. In this way, the

list di ends up gatting the values of y0, ∆y0, ∆2y1 . . . , ∆n−1y0 in succession. These, of

course are precisely the coefficients of the various factorial polynomials that feature in

the Newton interpolation scheme.

One very important point to note is the very first line of the difference() function

Page 136: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 8

li = l[:]

We have already met the construct l[:] - this is the slice of a list which returns

every elements of the list. So, the list li, on which all the subsequent differencing

operations take place, is a different one from the list l being passed as athe argument

to the function. This is actually essential if we do not want our argument list to be

changed by the function itself. Note that the list li is being defined in the function

difference() and hence is a local object. Even though we do have a list named li in

the main program, this one is distinct from it and hence changing li in the function

does not change the global list li. Of course, if we wanted to evaluate the interpolating

polynomial at several different values of x (instead of once as in the program) we would

have to be very careful to ensure that the global list li does not change everytime the

interpolation function is called.

A note on using lists in functions

All this may be a bit confusing to you if you had payed careful attention to the swap()

function discussed on page 11 and the subsequent discussion on namespaces. After all,

isn’t the argument that you pass to the function a local variable, so that changing it

within the body of the function has no effect on the value it has in the main program?

Actually the thing is a bit more complicated than that! To see what really happens,

let us take help of the id() program, which, remember, returns the memory location

of a paricular variable. We have already seen the following :

> > > x = 5

> > > print id(x)

143208596

> > > x = x + 10

> > > print id(x)

143208488

Page 137: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 9

which tells us that the variable that is assigned the value x + 10 is not the original

variable x, but a new one! Now let us try doing the same thing inside a function.

> > > def ch(x):

... print id(x)

... x = x + 10

... print id(x)

...

> > > x = 5

> > > id(x)

143208596

> > > ch(x)

143208596

143208488

This shows that the variable x that is passed to the function ch() is actually the same

as the global variable x! However, when you assign the value x + 10 to x, the value is

assigned to a new variable - the same as before! There is really no diffrence between

whether you do the assignment inside a function or outside it - each time you use the

assignment statement you create a new variable! This also works for lists - as the

following will show you :

> > > def ch(l):

... print id(l)

... l = []

... print id(l)

...

> > > l = [1]

> > > id(l)

143832268

> > > ch(l)

Page 138: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 10

143832268

143390548

> > > l

[1]

As you can see, the list that was passed to the function ch() is actually the same list

as the one available globally. However the one that is assigned the value [], despite

having the same name, is actually a new object altogether! Of course, this is reflected

by the fact that the value of the list in the main program stays the same after the

function ch() has been executed. So far, there seems to be no difference between the

other kinds of variables and lists. The big difference comes in when you try assigning a

value not to the whole list, but to an element of the list.

> > > li = [1,2,3]

> > > id(li)

164473900

> > > li[0]=0

> > > id(li)

164473900

As you can see, the identity of the list object stays the same! This means that the list

that has been changed is the same list and not a copy! The same thing happens when

you change an element of the list inside a function :

> > > def ch(l):

... print id(l)

... l[0]=0

... print id(l)

...

> > > ch(li)

164473900

Page 139: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 11

164473900

> > > li

[0, 2, 3]

As you can see, the object that is passed to the function is the same as the one in the

main program. What makes the big difference in this case is that, as we have already

seen, changing an element in the list by an assignment does not change the identity

of the list. The upshot is that when we change the list passed as an argument to the

function in this way, even the original list in the main program is changed!

Apart from assigning a value to a particular element of a list, there are quite a few

other operations that change a list in place, so that using these operations in a function

has the often unintended side effect of modifying the original list. Of course, you may

often want to modify the list - and then this behaviour is precisely what is wanted.

Confused with all these details? Don’t worry - this tends to cause trouble for even the

best of us! There is one simple way, though! If you do not want the original list to

change as a result of operations carried out in a function, just make an exact copy of

the list passed to the function and carry out all operations on that. That way, you are

safe from unintentionally changing the original list.

7.2 Lagrange interpolation

As ypu have seen, Newton interpolation is a very convenient way to calculate interpo-

lated valiues of the co-location polynomial, provided that the data points available are

equally spaced. We may of course not be fortunate enough to land up with equally

spaced data everytime - so we need a more general method of evaluating co-location

polynomials. Given a set of n data points (xi, yi), i = 0, . . . , n − 1 it is convenient to

define the n Lagrange polynomials Li (x) , i = 0, . . . , n− 1 by

Li (x) =(x− x0) (x− x1) . . . (x− xi−1) (x− xi+1) . . . (x− xn−1)

(xi − x0) (xi − x1) . . . (xi − xi−1) (xi − xi+1) . . . (xi − xn−1)

Page 140: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 12

It is easy to see from the definition that the n-th degree polynomial Li (x) vanishes for

all values of xj where data points are provided, except for xi. It is also obvious that the

denominator is designed to ensure that the polynomial takes the value unity at x = xi.

Thus, we have

Li (xj) = δij

A moment’s thought will tell you that this means that the colocation polynomial that

we want is given simply by

f (x) =n−1∑i=0

yiLi (x)

As can be easily checked, this polynomial does satisfy f (xi) = yi for each of i =

0, 1, . . . , n− 1.

A program that can implement the calculation of the Lagrange interpolating polynomial

(where we use the same data set as the one which we used to illustrate the Newton

interpolation method) is given below :

def lagrangePoly(x,i,xdata):

xlist = xdata[:i] + xdata[i+1:]

xi = xdata[i]

poly=1.

for xj in xlist:

poly = poly*(x-xj)/(xi-xj)

return poly

xdata = [0,1,2,3,4,5]

ydata = [12,8,5,9,11,7]

x = input(’Enter the value of x : ’)

ipt = 0.0

for i in range(len(xdata)):

ipt = ipt + ydata[i] * lagrangePoly(x,i,xdata)

print ’The interpolant at x = ’, x , ’ is ’ , ipt

Page 141: Programming for Physics in Python

CHAPTER 7. ... INTERPOLATION 13

The program is more or less self-explanatory. Note the construction xdata[:i] +

xdata[i+1:] near the beginning of the function lagrangePoly() though. Of course

the two terms xdata[:i] and xdata[i+1:] are list slices which we have seen before.

The first is a list that has all the elements of xdata from the beginning till before the

ith, while the second has all the elements from number i + 1 onwards till the end. The

+ sign between the list is a list concatanation operator. It’s job is to join the two lists

together. Thus the list xlist produced ends up having all the elements of the list xdata

except the ith. You should convince yourselves that this is exactly what is needed to

build up the Li (x).

A typical run of this program looks like

$ python lagrangeIntn.py

Enter the value of x : 3.5

The interpolant at x = 3.5 is 10.8515625

7.3 Spline interpolation

Useful as the above two interpolation methods are - they do suffer from one major

weakness. Let me illustrate this with an example.

Page 142: Programming for Physics in Python

Chapter 8

Simulating random processes

Quite a few physical processes are subject to the laws of chance. The first example that

comes to mind is perhaps radioactive decay. However, there are many more physical

situations where random processes play an important role. Let us start with an example,

though, that has relatively little to do with physics, and more with mathematics - the

estimation of the value of π by using random processes.

8.1 “Measurement” of π

Consider a game where you scatter sand grains randomly on a square. What fraction

of these will land up inside the quarter-circle as shown in the figure? The answer, of

course is the ratio between the area of the quarter circle and that of the square, which isπ4. So, if one were to do this experiment, the resulting fraction will give us an estimate

of the number π. Of course, this supposes that the person doing the scattering can

really do it uniformly - otherwise our result will be off by a huge margin.

Instead of actually carrying out this experiment with real grains of sand, we can put

the computer to simulate the scattering and the counting. Not only will this be less

of a strain on your hands, it is almost certain to be a lot more precise. In order to

do this , we must make the computer toss imaginary grains of sand randomly inside a

1

Page 143: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 2

unit square. since a random point inside the unit square has two random coordinates,

both lying between 0 and 1, the random() function from the random module that we

have already met earlier is just right for the job. All that is left is keeping track of how

many points have been chosen in this way, and counting the number of those which

land up inside the target area. For the latter, all you have to do is check whether the

coordinates satisfy x2 + y2 ≤ 1. A program that will do the job is

from random import random

Nin = 0

Ntot = input("Enter the total number of points : ")

for i in range(Ntot):

x = random()

y = random()

if x*x + y*y <= 1.0:

Nin = Nin + 1

print "Estimated value of pi is ",float(Nin)/float(Ntot)*4

The program is simple, indeed! The only thing that is really important here is to note

the use of the float() function to convert the variables Nin and Ntot to floating point

variables from integers. Remeber, this is essential - otherwise the ratio will always

vanish because of integer division (except in the unlikely situation where all the points

land inside the target - giving an estimate of 4 for π). Of course, using the float()

function in both the numerator and the denominator is an overkill - because of the way

python handles mixed mode arithmetic one would have sufficed.

A typical run using this function may look like :

$ python pi.py

Enter the total number of points : 100

Estimated value of pi is 3.28

$ python pi.py

Enter the total number of points : 1000

Page 144: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 3

Estimated value of pi is 3.148

$ python pi.py

Enter the total number of points : 10000

Estimated value of pi is 3.146

As you can see, the estimated value tends to get closer to the correct value of π as the

total number of points being considered increases - which is expected for any calculation

based on probabilities.

8.2 Radioactive decay

The next example that we are going to consider is one that has much more to do with

physics. This is the problem that we are all familiar with from high school - that of

radioactive decay of a bunch of unstable nuclei.

As you know, whether a particular nucleus will decay in a given interval of time is

completely random and is controlled by the decay constant λ which is equal to the

probability that it will decay in a unit interval of time. Thus, the number of nuclei that

deacy in a tiny interval of time dt is given by λNdt where N is the number of nuclei

present at that time. So, the number of nuclei obeys the equation

dN = −λNdt

and so we arrive at the familiar equation for radioactive decay

dN

dt= −λN

which has the solution

N = N0e−λt

- the familiar exponential decay law.

Page 145: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 4

Let us put the computer at work to actually simulate the radioactive decay of nuclei.

Here, we do not try to solve the differential equation (although we could of course

have gone about this job using the same technique as for the problems of dynamics

that we have tackled earlier) but rather use the random() function to decide whether

a particular nucleus is going to decay or not! So, for each time interval, we take one

nucleus at a time, and choose whether to leave it intact or let it decay depending on a

random number. A program to do this could be :

from random import random

f = open(’decay.out’,’w’)

Ntot = input(’Enter the total number of nuclei : ’)

lam = input(’Enter the decay constant : ’)

t = 0

tf = 100

while t < tf:

t = t + 1

for i in range(Ntot):

x = random()

if x < lam :

Ntot = Ntot - 1

print > > f,t,Ntot

Note that we are assuming here that the random() function produces random numbers

between 0 and 1 uniformly. If this is correct, then a particular number will be less

than λ with probability λ - which is just what we need to check to decide in favour

of a particular nucleus decaying. Also note that the list denoted by range(Ntot) is

produced the first time the header line of the for block is encountered (which means

once in every run of the while loop) - this means that decreasing the value of Ntot in

the middle of the for loop does not prevent one from checking every one of the nuclei

for decay!

Figure (8.1) shows the results from this program. For both of the curves in the figure

we have taken λ = 0.03. Where the two curves differ is in the initial number of particles

Page 146: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 5

Figure 8.1: Output produced by the program decay.py for a decay constant λ = 0.03.The left hand curve is data for N0 = 10000 while the right hand one is for N0 = 1000.Note the bigger deviations from the exponential curve (solid line) in the second case.

- where the left hand one has N0 = 10000, the right hand one is for N0 = 1000. As you

can see, the first one compares rather favourably with the theoretical curve, while the

deviations are larger for the latter data. This is only to be expected. Remember, the

theoretical curve is based on probabilistic calcuations - and these work well as long as

you have a large number of nuclei to use them on.

At this point, it may be useful to point out one important distinction between this

simulation and that of, say, the falling particle under drag. There, we had to solve a

differential equation - a task that we performed numerically. Of course, the differential

equation was written down after making a few physical approximations (like neglecting

the rotation of the earth and considering a linear realtionship between the drag force and

the velocity). However, the subsequent calculations could have been done exactly - this

is precisely the step that we carried out approximately using the program. In contrast,

as far as the problem of radioactive decay is concerned, the differential equation is

actually an approximate descrption of the actual process - it is our simulation that is

closer to the actual process of decay.

Page 147: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 6

8.3 The random walk problem

The random walk is one of the standard problems in the theory of random processes.

In its simplest version, this problem consists of an object, the “random walker”, that

can execute a random step either to the right or to the left at each successive instant

of time.

Let the probabilities of the rightward step and a leftward step are p and q, respectively.

We must of course have p + q = 1. Now, out of the 2N walks of length N that are

possible, NCN+ walks consists of N+rightwards and N− = N −N+ leftwards steps. Of

course, all these walks lead to a final displacement of d = 2N+ − N steps from the

origin. Again, the probability that a particular random walk of length N has exactly

N+ rightwards steps in a particular order is given bypN+qN− , where we have assumed

the steps to be independent of each other. Since all these wlaks are mutually exclusive,

the net probability that a walk of length N has exactly N+ rightwards steps is given by

PN (N+) =N !

(N+)! (N−)!pN+qN−

or, in terms of the final displacement,

PN (d) =N !(

N+d2

)!(

N−d2

)!(pq)

N2

(p

q

) d2

This is called the binomial probability distribution for obvious reasons. Using this

probability distribution we can calculate the various statistical properties of a walk, like

the mean displacement, the root-mean-square displacement and so on. It is somewhat

simpler to calculate the mean, rms values etc. of N+ and one can easily derive the

corresponding values for d from them.

The sum of all the probabilities is, of course, 1. For reasons that will be clear soon, at

this stage I prefer to write this sum as a function of the two probabilities p and q, and

Page 148: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 7

choose to ignore the fact that these two add up to 1. This defines the function

f (p, q) =N∑

N+=0

PN (N+) =N∑

N+=0

N !

(N+)! (N−)!pN+qN− = (p + q)N

where the last step follows from the binomial theorem. Using the fact that p + q = 1

leads to this sum being 1, as it should. Now, the mean value of N+ is

〈N+〉 =N∑

N+=0

N+PN (N+)

=

[p

∂pf (p, q)

]p+q=1

= Np (p + q)N−1 = Np

whereas the mean-squared value can be calculated from

〈N+ (N+ − 1)〉 =N∑

N+=0

N+ (N+ − 1) PN (N+)

=

[p2 ∂

∂p2f (p, q)

]p+q=1

= N (N − 1) p2 (p + q)N−1 = N (N − 1) p2

so that ⟨N2

+

⟩= N (N − 1) p2 + Np

The above manipulations should make it clear why I had chosen to define the func-

tion f (p, q). Now, it should be pretty easy to calculate the mean and mean-squared

displacement

〈d〉 = 〈2N+ −N〉 = 2 〈N+〉 −N = (2p− 1) N⟨d2⟩

=⟨4N2

+ − 4N+N + N2⟩

= 4⟨N2

+

⟩− 4N 〈N+〉+ N2

= 4N (N − 1) p2 + 4Np− 4N2p + N2

Page 149: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 8

= 4Np (1− p) + [(2p− 1) N ]2

This means that

(∆d)2 =⟨d2⟩− 〈d〉2 = 4Np (1− p)

All these predictions can be verified by simulating a large number of random walkers on

the computer, “measuring” their dispacements and calculating the average and mean-

squared values.

Another thing that can be checked directly in this case is the theoretical prediction

of probabilities. If we keep track of the final displacement of the large number of

random walkers, and count the number of them with a particular displacement d, we

can “measure” the relative frequencies of these displacements. If the number of random

walkers involved is very large, then these relative frequencies can be expected to mirror

the theoretical probability distribution rather accurately.

In order to simulate the large number Nw of random walkers, we start with a list

containing exactly Nw zeroes - signifying that all the random wlakers start from the

origin. Then, at each time step we take the list elements one by one, generate a random

number, and either add or subtract 1 (correpsonding to moving either right or left)

depending on whether this random number is more than p or not. Thus at each step we

get the position of each of our Nw random walkers - from which we can get the values

of 〈d〉 and 〈d2〉 as a function of the number of steps.

import math

from random import random

f = open(’rwres.out’,’w’)

g = open(’rwdist.out’,’w’)

Nw = input(’Enter the total number of random walkers : ’)

p = input(’Enter the probability of a rightward step : ’)

N = input(’Enter the number of steps : ’)

Page 150: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 9

n = 0

rw = []

#initialize

for i in range(Nw):

rw.append(0)

while n < N:

n = n+1

tot = 0.

totsq =0.

for i in range(Nw):

x = random()

if x < p:

rw[i] = rw[i]+1

else:

rw[i] = rw[i]-1

tot = tot + rw[i]

totsq = totsq + rw[i]**2

mean = tot/Nw

meansq = totsq/Nw

var = math.sqrt(meansq-mean**2)

print > >f, n, mean, meansq, var

for d in range(-N,N+1,2):

print > >g, d,rw.count(d)/float(Nw)

In this program, we have opened two output files for writing to, called rwres.out and

rwdist.out, respectively. Note that by selectively redirecting the output to either of

the two file handles f or g we can write to either of the two files as desired, thus enabling

us to organise the output better.

The final loop runs over all values of d from +N to −N (remember, the list stops one

Page 151: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 10

short of the second argument) in steps of 2. Remember that these, precisely, are the

allowed values of d for a walk N steps long. Inside this loop, we meet a new function that

can be used with lists. The list.count() takes a single argument and counts the number

of times this argument appears in the list. Dividing this by the value Nw (remembering

to convert at least one of the two using float() to prevent integer division) gives us

the relative frequency - which we expect to reflect the probability distribution.

The figure() shows the results obtained from this program for N = 100, p = 0.5. We

have used Nw = 10000, a number large enough to ensure that the probabilistic cal-

culations can be expected to be pretty accurate. Figure (a) seems to show that the

mean value fluctuates rather wildly with N . A closer look will show that it is really

the autoscaling employed by gnuplot that is to blame here - the actual mean is pretty

close to zero, as our probability based calculations would tell us! Figure (b) gives us a

the variation of the mean-squared displacement with N , this agrees quite well, as you

can see, with the theoretical prediction of 〈d2〉 = N that is valid for this choice of p.

A plot of the relative frequency data reveals that the probability is strikingly close to

the Gaussian probabilty distribution

P (x) =1√2πσ

exp

(−(x− x)2

2σ2

).

In fact, it can be proven quite rigorously that when all the three quantities N, N+

and N− are large, the binomial distribution appraches the Gaussian one rather well.

This is a special case of a very general result that goes under the name of the central

limit theorem - which says, roughly, that under suitable conditions most probability

distributions reduce to the Gaussian one. Rather than go through the actual calculation

that shows the limit rigorously, let me just point out that if a Gaussian is to satisfy the

correct mean and mean squared values, then its form must be

PN (d) =1√

2π (∆d)exp

(− d− 〈d〉

2 (∆d)2

)Using the built in fitting programs available in gnuplot, we can try to fit a Gaussian to

Page 152: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 11

the data that our program writes to the file rwdist.out. Fitting curves to data is a very

important topic in computation, and in a latter chapter we are going to take a look at

some of the myriads of techniques available for this. for the time being, let me tell you

how to use the built-in fitting function in gnuplot. For the data at hand you have to

do the following

gnuplot> f(x) = a * exp(-(x-b)**2/c)

gnuplot> a = 0.1

gnuplot> b = 0

gnuplot> c = 200

gnuplot> fit f(x) ’rwdist.out’ via a,c

In the first line above, we define the function that is to be fitted (note that in gnuplot,

the independent variable is always called x). In the next three lines we supply some

reasonable initial guesses for the parameters a,b and c. since the process of nonlinear

curve fitting is a very complicated one, even the computer needs all the help that you

can give it, so please try to supply reasonably close values! Finally, we ask the gnuplot

fitting program to get the form of the parameters that best fit the data present in the

file rwdist.out. note that in order to get a better fit, the value of one or more of the

parameters a, b or c must be varied - the via part of the fit command actually tells

gnuplot which of the parameters do we allow to vary. In the example above, we are

giving gnuplot the license to vary the parametrs a and c and try to arrive at those

values for which the curve fits the data the best. Note that since via does not mention

the parameter b, the fit program will not vary it - so that it stays fixed at the value 0

we had supplied for it1.

If you have followed the last few sections closely, you must have realized why the

program to calculate π that we started out with was not coming very close to the

actual value. All we had done was look at a single “experiment” - and find out what

1Actually the reason that I decided to omit the b from the parameter list of via is that I amreasonably sure that it is, actually, 0 - after all the Gaussian will be centered around the mean value.Of course, if allowing the value of b to vary to leads to a considerably improved fit, I will have toconsider the possibilty that my random number generator is not really working as it should!

Page 153: Programming for Physics in Python

CHAPTER 8. SIMULATING RANDOM PROCESSES 12

fraction of the “sandgrains” landed inside the target in that. It would have been better

if we had considered hundreds of such experiments and taken the mean of those results

- this would have given us a much closer value! Try to rewrite our program to see

whether this really works out in practice.

Page 154: Programming for Physics in Python

Chapter 9

Even more python - taking the

drag.py program further

In this chapter we will look into a few more details of the python language. To motivate

several of the new concepts that we study here we will fall back upon an old friend -

the drag.py program.

9.1 Handling files II

In section (4.4), we saw how to redirect the output of a program from the standard

output to a file from within the program itself. Useful and simple as this method is, it

does leave a lot to be desired as far as flexibility is concerned. In this method you don’t

really have much control over what the output will look like on the file. While that is

not a concern as far as the program that we are writing now is concerned (after all, all

that we need is to ensure that gnuplot can read the file properly, and gnuplot does not

really care about whether the data that is written pleases the eye or not), this will be

quiet important for some other applications. In such situations, as well as others where

you need a lot more control it is a good idea to use some of the built in methods that

the file object comes with - as we will now describe.

1

Page 155: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 2

Remember that the variable f in the statement

f = open(’drag.out’,’w’)

actually refers to a file object. Objects come with built in methods (which we know as

functions) - in this case the method that interests us is simply the write method. Due

to conventions in vogue in OOP that you will understand in detail later, the function

that writes to the file referred to by f is called f.write. If we want to write a nice

welcome message to our file drag.out, we may write a line like

f.write(’# Output of the program drag.py \n\n’)

after the line opening the file. This will write the nice message in the first line. In case

you are wondering about the # - it is part of the string being written and so does not

change the rest of the line into a comment! The reason why we have it in the first place

is to ensure that Gnuplot does not trip up on this when you try to plot the contents of

drag.out.

The \n\n at the end of the string is an example of an escape sequence - the \ gives

a whole new meaning to the following character (n in this case). Those of you who

have done C programming before would know that \n stands for the newline character.

Without this at the end of the string any subsequent output will start from the end

of the message - with the \n, the next output is written to the next line. Can you

understand what the double \n\n combination does to your output?

Coming to the major output from your program, the trouble is, we want to write four

numbers t, v, x and v-vex to each line, while the method f.write takes only one

argument and that too - a string! The way around this problem is to use a format

string. Before we get into format strings, however, we must understand tuples - which

we had just touched on when we talked about functions that return multiple values.

Tuples may already be familiar to you from mathematics. They are an ordered set of

objects. For example, a vector can be represented by three numbers (its components) -

Page 156: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 3

and the order of these numbers matter (you wouldn’t want to mix up the X component

with the Y - right?), so a vector is an example of a 3-tuple. Very much in the same

vein, a tuple in python is an ordered set of objects, and in python we write a tuple as a

comma separated list within round brackets. So, the following are examples of python

tuples :

(10, 15.0, 17)

(’A’,’B’)

(’A’, 11)

(a, b, ’Hi’)

As you must have noticed from these examples, the elements of a tuple can be of different

kinds (unlike components of a physical vector). In the last example, trying to define the

tuple will give a NameError unless the variables a and b have been previously assigned

values1. There is another data type in python which also deals with an ordered set of

objects - this is the list. Python does list processing wonderfully well - this being one of

the features it has inherited from the list processing language Lisp. For the time being,

the major difference between a list and a tuple is that although lists can be modified

once they have come into existence, tuples can not - they are immutable. This may

seem to put severe restrictions on their usefulness, but they are really rather useful for

several rather technical reasons. As of now, the major reason why we are interested in

tuples is that they are necessary to specify the argument list in a formatted string. For

example, the line of code that we will use to write data to our file drag.out is given by

f.write(‘%f \t %f \t %f \t%f \n’ %(t,v,x,v-vex))

Note that here argument of f.write is actually an expression. The % sign separating

the string ‘%f \t %f \t %f \t%f \n’ and the tuple (t,v,x,v-vex) is actually an

operator - the format operator. Here you are seeing an example of operator overlaying

1Remember - when you refer to ’a’ you are talking about a string which has the single charactera in it, when you refer to a you are referring to that variable a!

Page 157: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 4

- the same symbol stands for different operations depending on what the operands

are. The % symbol, sandwiched between two integers is the modulus operator, 10%3

returns 1 - the remainder when 10 is divided by 3. On the other hand, if the first

argument is a string, % acquires the meaning of the format operator. Actually, you

have already seen examples of operator overlaying, but perhaps without realizing it!

For example, the operator + in the expressions 10 + 3 and 10.0 + 3.0 actually perform

quite different tasks internally (since integers and floating point numbers are represented

quite differently), although their logical effects are almost the same. You have also seen

the symbol + being used in the concatanation (joining together) of two lists - where

the logical neaning of the operator is quite different!

Returning to the format operator - it takes two arguments, the first of which, as we

have seen, has to be a string - called the format string. The second argument is a tuple

of expressions (remember even a single variable on its own is also an expression). The

result is a string, in which the values expressions has been formatted according to the

format string. The %f above is an example of a format sequence, which tells python

to format the corresponding expression as a floating point number. The \t, as C users

will already know, is the tab character which moves the cursor to the next tab stop.

So, in this case the output will be neatly set in four columns, which tell us the value of

t,v, x and v-vex, respectively.

Format strings can get much more sophisticated than this. You can of course format

other kinds of data - %s stands for string, while %d stands for integers. You can also

specify the minimum space a particular item will take by adding a number after the

% sign. If the item is smaller in size, leading spaces are added to pad it up. If you

want trailing spaces add a - sign before the integer! For a floating point number, you

can also specify the number of places after the decimal point that will be displayed -

%-10.2f means a floating point number that will take up 10 spaces (adding trailing

spaces if necessary) which will also be rounded to two decimal places. We will use such

formatting strings to write a few descriptive lines to our output in the code below.

Once the loop has exited, we need to add one more line before the program ends :

f.close()

Page 158: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 5

which obviously closes the file drag.out. So, the improved version of our program looks

like :

import math #imports the math module

f = open(’drag.out’,’w’) #opens the file drag.out, for writing

# function that calculates the force

def force(vel,pos,time):

f = m*g - k*vel

return f

#function that implements the Euler algorithm

def update(v,x,t):

a = force(v,x,t)/m

v = v + a*deltat

x = x + v*deltat

t = t + deltat

return v, x, t

# set parameters

m=input(’Give me the mass : ’)

g=9.81

k=input(’Give me the drag coefficient :’)

#set initial conditions

ti=input(’What is the initial time? ’)

tf=input(’What is the final time? ’)

deltat=input(’Tell me the time interval : ’)

Page 159: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 6

t=ti

x=input(’Give me the initial position : ’)

v=input(’Give me the initial velocity : ’)

# Write nice header lines for the output file

f.write(’# Output of the program drag.py \n\n’)

f.write(’# Values of the parameters used :\n’)

f.write(’# m = %-10.3f g = %-10.3f k = %.3f \n\n’ % (m,g,k))

f.write(’# Initial conditions at t = %.3f :\n’ % ti)

f.write(’# x = %-10.3f v = %-10.3f\n’ % (x,v))

#The loop where all the work gets done

while (t<tf):

v,x,t = update(v,x,t)

# Calculate the exact value of v

vex = m*g/k*(1-math.exp(- k*t/m))

# write the output to drag.out

f.write(’%f \t %f \t %f \t%f \n’ %(t,v,x,v-vex))

f.close() #Closes the file drag.out

9.2 Improving the input routine

9.2.1 The raw input() function

We have just learned how to handle inputs from the keyboard via the input() function.

However, if you use the program a few times, you may begin to feel slightly bored with

having to enter the same parameter over and over again, especially if you are changing

them only one at a time (Even if you want to change only the initial position, you have

Page 160: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 7

to keep on giving the values for all the other parameters and initial conditions!). One

way out of this is to allow a default value for each (or some) of the parameters, which

will be assigned to it if the user just hits the <Enter> key when asked to supply a value.

The trouble is, if you try to use the input function, hitting the <Enter> key gives you an

error! So, it is up to us to write a new function that will do the job for us. Fortunately,

we can use the python builtin function raw_input() instead of input() - the advantage

of the former is that hitting the <Enter> key will return an empty string (also called a

null string by show-offs), instead of an error. However, raw_input() treats whatever

you type at the keyboard as a string, while the data you want to supply to your program

are real numbers (floats). The way out is to use the float() function, which changes

a string (or an integer) to a floating point number.

A possible code for the my_input() function can be

def my_input(prompt,default):

a = raw_input(prompt)

if a == ":

return default

else :

return float(a)

The logic behind this function should be clear enough - the function uses the builtin

raw_input() function to store the users response in the variable a. If the user hits

<Enter> instead of typing in a value, the value assigned to a will be the empty string

" and then the if - else block will ensure that the default value is returned.

9.2.2 Default and named arguments

Though this program works well enough for most purposes, one or two problems may

be apparent to you. What if I did not want to supply a default value? In that case

I would like to be able to call the function my_input() with only one argument - just

the prompt. However, if I supply one argument to the function as it has been written

Page 161: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 8

down - the interpreter is going to complain. Python, however, allows you a nice way

out - while defining the function, you can supply default values for the arguments! All

you have to do is to change the first line defining your function to

def my_input(prompt=",default="):

In this case, we have supplied default values for both our arguments. We have chosen

null strings for both - but you have the option of choosing any legal value that you

can think of! In this case, if we supply only one argument, it is passed to the first

argument, prompt in this case, whereas the variable default gets its default value -

the null string. You may wonder why we have supplied a default value for the first

argument too. After all, if we call my_input() with only one argument, doesn’t it

automatically get assigned to prompt? Of course, if you ever wanted to input a variable

without prompting the user, and on top of that, you had no default value in mind, you

could call the function my_input() with no arguments (Remember, you have to supply

the braces, (), even if you were calling the function with no arguments!). However, it

would be even more useful if you could have cases where you can supply the value of

the second argument only while calling the function, with the first variable getting its

default value. The trouble is, if you supply one argument, how can the poor interpreter

know that you really meant it to be assigned to the second variable and not the first

one? Here, too, python comes up with a nice surprise - you can actually name the

argument while calling the function. So, if you call the my_input() function as

m = my_input(default=10.0)

the variable default gets the value supplied, 10.0 while prompt gets its default value

- the null string. The extra flexibility offered by this ability to actually specify the

variables makes using functions in python quite a matter of joy!

Page 162: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 9

9.2.3 String concatanation

You might want to modify the prompt that my_input() is going to give the user - so

that the user gets an idea of what the default value specified is. One way to do this

would be to replace the line calling the raw_input() function by

a = raw_input(’%s (%f)’ % (prompt,default))

where we see the format operator % in operation again. There is another, more direct

way of building up the prompt - you just join the string prompt to the string ’ (’

followed by str(default) and then by the string ’)’. In the above the function

str(default) converts the value stored in default to a string. How do we join the

strings together, though? Rather intuitively, the operator you use to join strings in

python is +. Here you see another example of operator overlaying. The + operator

means addition if the operands are integers or floats, but when applied to strings it

becomes the concatanation operator, one which joins the strings together to produce a

single string. The job of joining strings together that we want to do can be carried out

by the expression

prompt + ’ (’ + str(default) + ’) ’

Note that you can only concatanate two strings - so trying to join default with ’ (’

would have given rise to an error. This is why you need to first convert default to a

string using the str(default) function call. So, the relevant line could also be written

as

a = raw_input(prompt + ’ (’+str(default)+’) ’)

Of course, it might be a better idea to store the new string in a variable and use this

variable as an argument to raw_input().

Page 163: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 10

9.2.4 A few more improvements

Since we now have provided the option of using the my_input() function without a

default value, you have to be slightly careful, though. As the prompt stands now, you

will get a pair of empty brackets when the default value is not specified. Of course, you

could get your function to check whether a value has been provided for default, and

only then build the new prompt.

One more problem needs to be rectified. What if the user just hits <Enter>, even when

asked for the value of a variable for which there is no default value? In this case, the

function will return a null string to the caller - and its almost certain that this will

cause your program to crash because of a type mismatch. One thing that you can do to

prevent this is to rewrite the function in such a way that it will detect such a situation

and refuse to let go until the user actually enters a value. A similar problem that may

arise is that the user inadvertently enters a data type other than a floating point or an

integer.

An improved version of the program that addresses these issues is

def my_input(prompt=",default="):

"""Provides an alternative to the python input() func-

tion. Takes two arguments, the prompt and the default

value. Returns a float. Keeps on repeating until valid in-

put is provided """

notDoneYet =1

if default == ":

newprompt = prompt

else :

newprompt = prompt + ’ (’ + str(default) + ’)’

while notDoneYet:

notDoneYet = 0

Page 164: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 11

a = raw_input(newprompt)

if a == ":

if default == ":

print ’No default value supplied - try again’

notDoneYet = 1

else :

return default

else:

try:

return float(a)

except ValueError:

print ’Wrong variable type - try again’

notDoneYet = 1

Note the while loop that we have put in, controlled by a variable suggestively called

notDoneYet (remember - python is case sensitive, so that whenever you refer to this

variable be careful to use the exact spelling). It is initially set to 1, so that the while

loop runs at least once. The moment we enter the loop, the first thing done is to set

notDoneYet to zero - so that if nothing untoward takes place within the loop, it will not

attempt to run again. However, if the user refuses to supply a value for a variable whose

default value has not been provided, (note the nested loop structure - the function first

checks whether a value has been supplied and if not, then checks whether a default

has been provided) then the function prints out a warning to the user, and also sets

notDoneYet back to 1. Thus as long as the user persists in hitting the <Enter> key,

the loop will keep on running!

9.2.5 Excception handling in python

A new construct that you are seeing here for the first time is the try-except : block.

This very powerful construct makes exception handling in python a real pleasure! Ex-

Page 165: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 12

ceptions, remember, are results of the interpreter talking back to you, warning you

about some possible errors in your program. technically speaking, we call this raising

an exception. For example, if you try out the following at the python shell :

> > > a=’1.0’

> > > b=float(a)

> > > b

1.0

The interpreter assigns the floating point number 1.0 (As opposed to the string ’1.0’

which we had stored in the variable a) in the variable b - as the result of the last

command shows. So, the job of the function float() is to convert a string that expresses

a number (with or without a decimal point) into a floating point number. But, if you

try

> > > a=’abcd’

> > > b=float(a)

The interpreter responds with

Traceback (most recent call last):

File "<stdin>", line 1, in ?

ValueError: invalid literal for float(): abcd

that is, it has raised the ValueError exception. What this is telling you (rather cryp-

tically, I admit) is that there has been a ValueError : you have tried to give the literal

string ’abcd’ as in input to the program float() - which is something float() can

not really handle. As I told you before, the details of the various possible exceptions

are stored in the exceptions module - which along with the __builtins__ module are

automatically loaded everytime the interpreter starts.

Page 166: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 13

Usually, when a interpreter raises an exception, the program halts then and there -

and you are left with a rather terse error message (like the ValueError: invalid

literal example above) which tells you just where the exception was raised. You can

then try to figure out just what went wrong by reading the exception message and fix

your program accordingly. Dn’t worry if the error message looks like gobbledygook now

- you usually get the hang of them once you write a few programs and (unless you are

very exceptional - pun intended) get to face a few of these exceptions.

However, there is a nice way of putting the exceptions to use. In our my input()

function, what we try to do is return the floating point version of whatever the user

entered (unless she has hit the enter key - in which case the default value is given back).

Remember that the raw input() function returns a string - so even if you had typed in

1.0, what the variable a gets as a result of the line

a = raw_input(newprompt)

is the value ’1.0’ - so converting that into a floating point value using the function float()

is necessary. If we had just used plain old

return float(a)

instead of the try...except block that we have actually used things would have worked

out fine - as long as you had typed in numbers like 1.0 or 2. In these cases, the variable

a would have been assigned the values ’1.0’ and ’2’ respectively, and the my_input()

function would have returned the floating point numbers 1.0 and 2.0 respectively. What

happens if you type in abcd (or some such string of characters) by mistake is that a

gets the value ’abcd’ and then the interpreter raises a ValueError exception when it

tries to figure out the value of float(a) - halting the entire program then and there.

This behavior of the interpreter is quiet useful, really! After all, you don’t want your

program to run with some nonsense as input and give you an answer, which you may not

even recognize as the garbage that it is! However, just think of the foloowing scenario

Page 167: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 14

- your program needs 10 inputs and you have given the first 8 correctly. In typing in

the 9th you make a slight mistake (maybe you have typed 1,0 instead of 1.0) and your

program halts then and there - which means you have to not only type in the value

where you have made a mistake again, but rather type in all the previous input data

again, too!

The way out of this mess is to handle the exception - so that instead of crashing, the

program takes corrective measures on meeting an exception. This precisely is what the

try...except block does. Put roughly, the block

try:

return float(a)

except ValueError:

print ’Wrong variable type - try again’

notDoneYet = 1

tries to return float(a), but if it meets with a ValueError the except ... : block

takes over and instead of halting the program the interpreter prints out the message

’Wrong variable type - try again’ and sets the notDoneYet variable back to one

- so that the loop runs yet again, giving you a chance to rectify your error!

9.2.6 Documentation strings

We are yet to explain one thing about the function - just what is the very first thing

that we see - a long string that is enclosed between a pair of three double quotes?

A triple quote signifies a multi-line string. Anything enclosed within a pair of triple

quotes is considered a single string - that includes any newline characters as well as

other quote marks. They can be used anywhere within your program whenever you

need a long string. However, you will see them used most often as the very first thing

in the body of a function definition - the first line after the colon. Placed here, the

triple-quote string assumes a special significance - it becomes the doc string for the

function - short, of course, for documentation string. This is where python gives you an

Page 168: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 15

incentive for writing good documentation. If you use IDLE, for example, you will get

context sensitive help. Whenever you type in the name of the function, IDLE responds

by showing you its doc string as a tool tip.

In conclusion, I would like to emphasize one thing. Our final version of my_input()

has grown to be quite complicated, with lots of conditions, and nested conditions being

checked. However, the strict indentation laws that python follows makes reading the

program quite easy - so that you can follow the logical flow quite simply. This, after

all, is one place where python scores significantly over other languages - here good

programming style is not a matter of personal preference, but mandatory!

9.3 Your own module(s)

Now that you have written down my_input(), how will you use it? One option is

simple, just type the function out at (or near) the beginning of the program drag.py

and you can call it from within the body. For example, the line reading in the value for

the variable m may now look like

m=my_input(’Give me the mass : ’,1.0)

In this case, the function will always return a value of 1.0 to m whenever, the user, faced

with the prompt

Give me the mass :

hits the <Enter> key.

The straightforward method outlined above will work, but it may not be the best

method for doing what is required! Note that the function my_input() is quite a

general purpose function, and it is not in any way directly linked logically to the task

that your program drag.py is carrying out. You may want to use the same function

in other programs that you write. Of course, you can cut and paste the relevant code

Page 169: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 16

to each program that needs to use it. If all you want to write is one such function,

then this is no problem. But soon you may built a repertoire of useful functions that

you have written, and cutting and pasting a large number of them can become quite

a bother. Again, if tomorrow you come up with a beautiful new modification for your

function (or come up with a solution for some bug it has), you will have to edit each

program where you have used it for the change to take place.

The way out is simple - instead of incorporating the code for the function in your

program directly, store it in another file - your own module! Save the function definition

that you have written in a file called, perhaps, myFunctions.py, in the same directory

as drag.py. Then all you will have to do is to add the line

from myFunctions import my_input

at the beginning of drag.py to be able to refer to the function my_input() from within

drag.py.

9.3.1 Storing your modules

As your programming experience grows, you can keep on adding more and more func-

tions to this module. Of course, it is a good idea to keep logically different kinds of

functions in different modules and name them accordingly. This, after all, is exactly

what python does for its own modules!

As you keep on writing more an more and more programs, you will prefer to store your

programs in different directories according to their logical structure. This will however,

create a new difficulty - you will have to keep a copy of the myFunctions module in each

directory where there is a program that accesses it. This defeats part of the original

purpose - maintenance is going to be quite a headache. To beat this, you may decide

to store all your modules in a central directory, maybe called myModules/, so that only

one copy will do. However, if the program drag.py is in some other directory (as it

should be, since it is not a module), it will not be able to find myFunctions and raise

Page 170: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 17

an error whenever asked to import it! The answer is provided in the sys module that

is builtin to python.

At the python shell type

> > > import sys

> > > sys.path

and the interpreter responds with

[", ’/usr/local/lib/python23.zip’, ’/usr/local/lib/python2.3’,

’/usr/local/lib/python2.3/plat-linux2’,

’/usr/local/lib/python2.3/lib-tk’,

’/usr/local/lib/python2.3/lib-dynload’,

’/usr/local/lib/python2.3/site-packages’,

’/usr/local/lib/python2.3/site-packages/Numeric’]

This is a list of directories, the current search path (what you will find will differ,

depending on the python version as well as how your system has been set up). The

square brackets that delimit it shows that it is a list, rather than tuple (which, as we

have seen before, are delimited by round brackets). Python will look through these

directories, in this order, to find a module named myFunctions.py - when asked to

import myFunctions. The empty quotes at the very beginning of the list stands for the

current directory, so that is exactly where python will look first of all. If you have root

access you can store your modules in any one of the above dictionaries and rest easy -

but most of us are not that fortunate. Moreover, even if you had root access, it is never

a good idea to tamper with the original installation!

Note that sys.path is a list in the true python sense of the word. If you ask the

interpreter for its type, it will tell you this!

> > > type(sys.path)

<type ’list’>

Page 171: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 18

Now is the time to take advantage of the distinction between tuples and lists that we

mentioned before. Tuples are immutatble, but lists can be changed. In particular if we

could add our /home/gauss/myModules directory to sys.path, python will know that

it should look there for the module myFunction. All you have to do is to begin your

programs with

import sys

sys.path.append(’\home\gauss\myModules’)

(of course, change the directory path to wherever your modules are) and then you can

merrily add

from myFunctions add my_input

and expect the program to work without any problems.

9.3.2 Testing your modules

Another important aspect of writing your own modules is testing them. Of course,

you can, as we have, import the module to some program and then use it within that

program to test whether it is performing as desired. However, once your modules keep

on growing in size and complexity, it would be a better idea if we could have some sort

of standalone testing mecanism within the modules themselves.

The modules that we have written are standard python files, so it is obvious that we

don’t have to stop at just writing down our functions in them. You can just as well

write lines of code that actually test these functions. The trouble is, these testing lines

will then have to be deleted (or commented out) before finally making the module open

for use, otherwise each time you try importing from the module in any actual program,

the test lines are also going to be executed! As always, python has a rather elegant

solution for this problem. The idea is simple, ensure that the testing code will run

only if the module is being run as a program in its own right and not when it is being

Page 172: Programming for Physics in Python

CHAPTER 9. EVEN MORE PYTHON ... 19

imported. It turns out that the interpreter actually can tell the crucial difference. All

modules have a built in attribute called __name__ (yes, that’s two underscores, followed

by name, followed by two underscores), and its value depends on how the module is being

used. When we import the module, the value of __name__ is just the filename, without

the extension. However, when a module is run as a standalone program, the value of

__name__ is ’__main__’ ! So, if we modify our myFunctions.py file by adding the

following lines

def my_input(prompt=",default=")

...

...

...

if __name__==’__main__’:

a = my_input(’Testing - supply value’, 1.0)

print a

Then the set of commands that form the body of the if block will be executed only if

the module is called as a standalone python program, while it will be ignored if the

module is imported by some other program.

Page 173: Programming for Physics in Python

Chapter 10

Getting more ambitious - dynamics

part II

Now that we have done poor drag.py almost to death, let us move back to physics.

Until now, we have stuck to one dimensional problems involving one particle - so that

we had to worry about only one position and only one velocity. If you think about it,

there is nothing that prevents us from modifying our program so that it works for more

involved situations!

10.1 Projectile motion

Our first example should have you howling in protest - after all this work on “advanced

things” - why go back to the motion that you have studied so thoroughly in high

school? We all know that the path of a projectile is a parabola - don’t we? Even then,

the projectile is usually our first foray in physics beyond linear motion - so it is quite

an obvious starting point in programming for advanced dynamics. It should be quiet

gratifying to see whether our program does allow us to recover the parabola! Of course,

even projectile motion can be much more interesting as well as challenging if we try to

make our projectile closer to real life - for example, by taking air drag into account.

1

Page 174: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 2

Let us try to write a program in which we will take the air drag to be −k~v where ~v is

the velocity of the projectile. One possible program for doing this will be :

# Program for calculating the motion of a projectile un-

der drag

from math import *

def force(x,y,vx,vy):

magv = sqrt(vx**2 + vy**2)

dragfx = -k*vx/magv

dragfy = -k*vy/magv

fx = dragfx

fy = - m*g + dragfy

return fx,fy

def update(vx,vy,x,y,t):

fx,fy = force(x,y,vx,vy)

ax = fx/m

ay = fy/m

vx += ax*deltat

vy += ay*deltat

x += vx*deltat

y += vy*deltat

t += deltat

return vx,vy,x,y,t

f = open(’proj.out’,’w’)

#parameters

m=1.0

g=9.8

k=0.2

Page 175: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 3

#initialise

t=0

x=0

y=0

vx=20

vy=20

height = y

idealHeight = vy**2/(2*g)

idealRange = 2*vx*vy/g

idealTimeOfFlight = 2*vy/g

f.write(’#Projectile motion with drag force\n\n’)

f.write(’#mass = %f, g = %f, drag coeffi-

cient = %f\n\n’%(m,g,k))

f.write(’#time\t vx \t vy \t x \t y’)

while y>=0:

vx,vy,x,y,t = update(vx,vy,x,y,t)

if y >= height :

height = y

print > > f,t,vx,vy,x,y

f.write(’\n#The range is %f, the height at-

tained is %f and the time of flight is%f\n’%(x,height,t))

f.write(’#Ideally these should have been %f, %f and %f respec-

tively \n’ % (idealRange,idealHeight,idealTimeOfFlight))

f.close()

Page 176: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 4

Figure 10.1: The path of a projectile. The outer blue line corresponds to the case wheredrag is absent. The inner red line is obtained with a drag coefficient of k = 0.05 Kg s−1

and mass m = 1.0 Kg.

This code shoud be more or less self-explanatory. In this case we run the while loop

until the projectile returns back to the ground (can you see why we had to use y >=0

and not y > 0?). The values of x and t at the end of the loop gives us the range and the

time of flight, respectively. As for the height attained, we need to find out the maximum

value attained by y during the flight. To do this, we start by assigning the initial value

of y to a variable called height. Each time the while loop runs, we compare the value

of y with the value currently stored in height. If the value of y is larger, we assign it

to height - if smaller, we do nothing. This way, it is easy to see that the final value

stored in height is actually the largest value y attains within the loop.

Page 177: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 5

Figure 10.2: The coupled mass-spring system

10.2 Coupled oscillations

Consider the coupled oscillations being executed by a pair of blocks resting on a smooth

horizontal table and connected by springs as shown in figure (10.2). It is easy to show

that the two displacements, x1 and x2satisfy the differential equations

md2x1

dt2= k (x2 − 2x1)

md2x2

dt2= k (x1 − 2x2)

In order to simulate this motion of this system, we have to modify our function and

update routine slightly. The force routine now will take four arguments (five, if you

consider time) in general, but in this particular example only two x1and x2 are necessary

and it will have to return two numbers. Similar modifications are necessary for the

update function.

def force(x1,x2):

f1 = k*(x2-2*x1)

f2 = k*(x1-2*x2)

return f1,f2

def update(v1,v2,x1,x2,t):

f1,f2 = force(x1,x2)

a1 = f1/m

a2 = f2/m

Page 178: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 6

v1 += a1*deltat

v2 += a2*deltat

x1 += v1*deltat

x2 += v2*deltat

t += deltat

Figure 10.3: Time versus displacement curves of the mass m1 for the four initial condi-tions mentioned in the text. Note the simple harmonic nature of the displacements forthe two lower curves.

Modify the rest of the program to produce coupled.py. Run this program for a suitable

range of time and δt, for the following initial conditions :

(a) x1 = 10, x2 = v1 = v2 = 0

(b) x2 = 10, x1 = v1 = v2 = 0

Page 179: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 7

(c) x1 = x2 = 10, v1 = v2 = 0

(d) x1 = −x2 = 10, v1 = v2 = 0

Carefully note the behaviour of displacement versus time in the four cases. Try to

explain the differences in behaviour, by going back to the equations and handling them

analytically.

You can play around with other initial conditions to see whether your explanation

works out. Also, try changing the degree of complexity of your problem (for example,

nonidentical masses or nonidentical springs) and see whether you can still understand

what you see!

10.3 Kepler orbits

Another example of motion with two degrees of freedom1 is the motion of a planet

around the sun. You must have learnt how to calculate the orbit using polar coordinates.

In what follows, we will use simple Cartesian coordinates to keep the computer routines

straightforward. We can keep the update routine unchanged from the previous example,

while modifying just the force routine.

def force(x1,x2):

import math

def r(x1,x2):

return math.sqrt(x1**2+x2**2)

r = r(x1,x2)

f1 = -G*m1*m2*x1/r**3

f2 = -G*m1*m2*x2/r**3

return f1,f2

1Actually, it is three, but angular momentum conservation confines the motion to a plane.

Page 180: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 8

Note that we have nested the function r(x1,x2) inside force(x1,x2). This makes

sense, because the only part of the program where r =√

x21 + x2

2 will be needed

is in calculating the force. This makes the function r(x1,x2) local to the function

force(x1,x2), though - any attempt to call r(x1,x2) from within the body of the

main program will lead to an error!

Complete the program and run it with suitable initial conditions. You may find it

necessary to go through the theory in order to figure out interesting initial conditions.

10.3.1 The Einstein correction

You may have heard that Newton’s theory of gravitation, though immensely succesful,

is only an approximation! A more precise theory is supplied by Einstein’s general

theory of relativity. The reason why no one had found any fault with Newton’s theory

for three hundred years, of course, is that the corrections that GTR introduces over

Newton’s thory is very small unless you are considering regions that are very close to

immensely dense bodies or bodies moving very fast. For slower bodies like the planets,

the corrections produced by GTR are really very very small - but they exist, nonetheless.

Indeed, celestial observations are carried out to such a high degree of accuracy that even

these effects are sometimes observable. A famous example is the well known precession

of the perihelion of Mercury - where GTR predicts that the point closest to the sun

in Mercury’s orbit will shift by about 43′′ of arc per century! Not only is this a very

small shift2, it is actually masked by much larger effects that occur because of the

presence of the other planets (notably Jupiter). So, it is quiet a tribute to the accuracy

of astronomical observations that we can actually detect effects that are this small!

GTR predicts that the motion of a small test body in the gravitational field of a spherical

static body of mass M is governed by an equation that reduces to the Newtonian one

for weak fields.

2Since Mercury is the closest to the sun, and the fastest among all the planets, the correction thatGTR induces for Mercury is actually the largest for all the planets!

Page 181: Programming for Physics in Python

CHAPTER 10. GETTING MORE AMBITIOUS ... 9

10.4 The Lorentz butterfly

So far, we have been talking about moving bodies. However, note that the same algo-

rithm could be adapted to solve other differential equations, too. For example, consider

the following set of three coupled differential equations

x = σ (y − x)

y = rx− y − xz

z = xy − bz

which were proposed in the late 1950s by MIT meteorologist Edward Lorenz as a very

simplified model of convection processes in the atmosphere. Here σ, r and b are param-

eters of the system. The detailed behaviour of the dynamics of this system depends

critically on the parameters.

Adapt our algorithm to solve the above equations for the values σ = 10, r = 28 and

b = 8/3. Start from a random set of initial values. Plot the variation of x versus z -

what do you notice?

Again start with two sets of initial conditions, differing by, maybe 0.001 for each variable.

What value is predicted for the final values at t = 10 for the two cases? Also, compare

the nature of the x versus z plot in the two cases.

One thing the above examples should have made clear to you. It would be a lot nicer if

we had a data type that could store vectors directly. Moreover, it would be a big bonus

if we could have routines that directly manipulate these objects, rather than having to

worry about components all the time. What we are asking for is a vector object - a

bundle of data and methods to manipulate the data. We will later learn how to write

such objects - which will make writing programs such as this one much simpler.

Page 182: Programming for Physics in Python

Chapter 11

A touch of class

Objects, as mentioned a while ago, are bundles which can carry both data as well as

procedures. We have already met such creatures before - remeber the file object that

we opened with the, ahem ..., open command? In python objects are implemented

via classes and instances. As the examples that follow will make clear - classes are

abstract entities while instances are concrete realisations of classes.

11.1 Barebones classes

The first class that we are going to write is one that will allow us to do algebra with

vectors. We will call this class, with a great deal of literary acumen, Vector. To define

a class is really simple - all you need to do is use the class keyword. The syntax looks

surprisingly like the def statement. The simplest possible version for our class is

class Vector:

pass

- that’s it! Remember, the pass statement does - absolutely nothing. One important

difference between the def statement and this one is that this one does not have a pair

1

Page 183: Programming for Physics in Python

CHAPTER 11. A TOUCH OF CLASS 2

of braces () to follow the name of the class. Once the class has been defined, you can

use it to create instances of the class, by simply using the name of a the class as if it

were the name of a function :

a=Vector()

Note that you must have the braces here! why this difference - you ask? Well, unlike

the case of function definitions and calls - the braces here mean quiet different things.

This will be clearer in a while - just read on!

So now we have a class and an instance. Unfortunately, our examples are ratehr barren.

Classes are meant to be bundles of data and methods - but our Vector example bundles

no data and no methods, at least so far. The same is true about its specific instance

a. Python actually provides you with a builtin function to peek into objects and see

exactly what attributes they carry - this is the dir() function. Try

> > > dir(Vector)

> > > dir(a)

in both cases the interpreter responds with a rather sparse list :

[’__doc__’, ’__module__’]

which tells us that there are only two attributes that are carried by both the class

Vector and its instance a - a doc string in the variable called __doc__ and the name

of the module they belong to in __module__ . Doc string? What doc string - you ask?

Well, there isn’t any - so asking

> > > print a.__doc__

returns None - which is what python calls the empty string. The same stays true of

the variable Vector.__doc__ . What about the name of the module? As of now, our

objects are not part of any module - they have been directly defined in the interpreter

itself! So, the name of the module they belong to is ’__main__’ - something we had

met before. Just try

Page 184: Programming for Physics in Python

CHAPTER 11. A TOUCH OF CLASS 3

> > > print Vector.__module__

and check for yourself. If you create another instance by typing

> > > b=Vector()

it will have exactly the same attributes - in other words all instances start life as exact

clones of each other.

Even this simple and rather barren example should serve as a reminder. We have seen

the ‘ . ’ notation before - remeber math.sin(), or f.write()1? Here, too, you have to

say a.__doc__ to ask the interpreter for the __doc__ variable carried by the instance

a - of course, if you wanted the doc string of the instance b, you should have asked for

b.__doc__ instead. Another reminder - variable names that start and end with two

underscores are used in python for variables with special meaning - as in __doc__ and

__module__ for our objects.

At this stage we have two instances a and b, both with rather trivial properties. We

can improve things somewhat, though! Remember, both the __doc__ and __module__

attributes of the class Vector are variables, and you can, of course change variables by

assitgning values to them! So, if you type

> > > Vector.__doc__ = ’empty vector!’

then the response to the three commands

> > > print Vector.__doc__

> > > print a.__doc__

> > > print b.__doc__

1Actually, these are not very disparate example. Although you could program in python withoutever bothering about objects - everything in python are actually objects! So the math module as wellas the f file handle are both objects - and hence the notation is actually uniform - you first name theobject the variable or procedure belongs to and follow it up with the name of the variable or procedureitself - with a ‘dot’ in the middle.

Page 185: Programming for Physics in Python

CHAPTER 11. A TOUCH OF CLASS 4

will all be identical - namely the interpreter will print out the string empty vector!

. It is natural to infer from this that assigning a value to a variable in the original

class affects the value of the corresponding variable in all its instances. This inference

is natural - but it is also wrong! To see this, try the following :

> > > id(Vector.__doc__)

> > > id(a.__doc__)

> > > id(b.__doc__)

and the interpreter will respond with a rather large number in all three cases. The

important thing that you should note is that it will give you the same number in all

three cases! so, what is the big deal? Well, look up the help on the function id() and

you land up with

> > > help(id)

Return the identity of an object. This is guaran-

teed to be unique among simultaneously existing ob-

jects. (Hint: it’s the object’s memory address.)

Thus an identical value returned by id(), means that the three variables are actually

identical! No wonder they return the same value! So, assigning a value to the class

variable does not affect the value in the instance variables - there are no such instance

variables to affect! When you call for a.__doc__ , the interpreter does try to find the

variable __doc__ belonging to a. Finding no such variable, it tries to look in the class

whose instance a is. Sure enough, it does find the variable __doc__ there! This is the

value that the interpreter returns for a.__doc__ . In technical language we say that the

instance a (and b, and all other instances of the Vector class you may care to create)

inherits the value of __doc__ from the Vector class.

So what happens if you assign something to the variable a.__doc__ ? The trouble is,

at this stage, there is no such variable! However, as we have been seeing from our very

first program - assigning a value to a variable for the very first time actually also creates

the variable! So, the command

Page 186: Programming for Physics in Python

CHAPTER 11. A TOUCH OF CLASS 5

> > > a.__doc__ = ’an empty Vector instance’

actually creates a variable __doc__ belonging to the instance a, and assigns to it the

string ’an empty Vector instance’. To see that this is actally what happens, try:

> > > print a.__doc__

an empty Vector instance

> > > print b.__doc__

empty vector!

To see that the point is really drilled home, let us take an “under the hood” look at

what is going on here. If you ask the interpreter to print the value of a.__doc__ it first

looks for a variable named doc belonging to a. It does find such a variable - and so

it prints the value contained in it. Try printing b. doc and the interpreter once again

looks for a variable doc belonging to b. Finding none there, it next looks at the

class Vector that b is an instance of. Sure enough, the Vector class does have a variable

called doc . So, the interpreter will merrily print that. In other words, instances

do inherit from their classes - unless you override this at the level of the insances. To

see whether you understand this one, just see whether you can explain the following

interactive session

> > > Vector.x=1.0

> > > a.x

1.0

> > > b.x

1.0

> > > a.y=2.0

> > > a.y

2.0

> > > b.y

Traceback (most recent call last):

Page 187: Programming for Physics in Python

CHAPTER 11. A TOUCH OF CLASS 6

File "<stdin>", line 1, in ?

AttributeError: Vector instance has no attribute ’y’

Once more, assigning a value to any variable for the Vector class has an imediate effet

We will soon see how to write our own classes. To get a feel of what class can do for

you, we will start with an example of a module where lots of classes hav already been

written for you to use. This module, as we will see, is also a very useful one for our

purpose of writing physics demos - it is officially called Vpython. The name by which

python recognizes the module, though - is visual.

Start the python interpreter, and type in

> > > from visual import *

and the interpreter will respond with the version of visual that you have in your system.

If instead, it responds with ImportError : no module named visual - you are out of luck,

at least until you download and install Vpython! If all goes well, type

a=sphere()

and up will pop a window with a big white sphere in a black background. What we

have just done is create an instance, a, of a class, sphere, that is provided for us by the

visual module. The object a, like all objects, carry data as well as procedures. To see

just what this bundle contains, type

> > > dir(a)

[’__class__’, ’axis’, ’blue’, ’color’, ’constr’, ’dis-

play’, ’frame’, ’green’, ’name’, ’pos’, ’radius’, ’red’, ’ro-

tate’, ’up’, ’visible’, ’x’, ’y’, ’z’]

what the interpreter does is give you a list of attributes that the object a has.

Page 188: Programming for Physics in Python

Chapter 12

Raising the accuracy - better

algorithms

As we have seen in the last few sections, the Euler algorithm is adequate for most pur-

poses, certainly for qualitative trends and quite often for quantitative calculations, too.

However, as byou must have guessed from the simple minded nature of the algorithm,

the method is not very accurate and can not be trusted for very precise calculations.

The obvious problem is that we are treating the acceleration in each small time interval

as a constant, where in reality it does change to some extent. Of course, it may seem to

you that the remedy is to make the time interval δt smaller and smaller. The trouble

with this is that if we need the answers to a very high degree of accuracy, then the in-

terval must be very very small - and so the number of steps required in the calculation

keeps on growing, making the whole process rather inefficient.

There is a much subtler problem with the growing number of steps that the computer

has to perform with decreasing step size. Remember, the computer does arithmetic

using real numbers only upto a finite number of places and hence to a limited degree

of accuracy. In most cases, the fact that this calculation is not 100% precise does not

bother us at all, the round off error being so very tiny. Carry out calculations over and

over again, and the round off errors begin to accumulate! As an illustration, try this

out at the python prompt

1

Page 189: Programming for Physics in Python

CHAPTER 12. ... BETTER ALGORITHMS 2

> > > a=1.0/99999

> > > s=0.0

> > > for i in range(99999):

... s=s+a

...

> > > s

0.99999999999841804

Notice that all the for loop does is to add a, which is defined to be 1.0/99999, to itself

99999 times. The answer, as every kid knows, is 1.0 - but the computer messes it up!

In case you are beginning to worry about the reliability of the calculations we have

carried out so far, let me point out that the error here is still very small - just 2 parts

in 1012! Imagine, though, what would happen if the calculation had to be carried out

many, many more times!

If you have paid careful attention, you may have already noted the effect of rounding

off of floating point numbers. If not, just look at the final value of the variable t in the

file drag.out - you may be in for a surprise!

So, merely decreasing the step size is not a very good way to actually improve the

accuracy. Not only does this slow down your program, it also begins to introduce

higher round off errors! At one point, the increase in accuracy due to the growth in

step size is actually offset by the increase in accumulated round off errors! The solution

is to look for more efficient and accurate algorithms - which will allow us to keep the

step size large, and hence the number of steps low, while retaining accuracy.

12.0.1 Taylor series and the Euler algorithm

So we want to improve upon the accuracy of the Euler algorithm. But just how accurate

is the Euler algorithm? To answer this, let us take a look at a derivation of the algorithm.

One way is to start from the Taylor series, which says

f (x + h) = f (x) + hf ′ (x) +1

2h2f ′′ (ξ)

Page 190: Programming for Physics in Python

CHAPTER 12. ... BETTER ALGORITHMS 3

= f (x) + hf ′ (x) + O(h2)

where ξ is a number in the interval (x, x + h). So if we are solving an eqation of the

formdy

dx= F (x, y)

we can find the value of y at the end of the n-th interval, yn+1 as

yn+1 = yn + hF (xn, yn) + O(h2)

This is the Euler algorithm, where we use the first two terms only two estimate yn+1.

It should be obvious that the third term tells us how far wrong we will be by using

the Euler algorithm. We are not really interested in the exact amount of error - just

the nature of its dependence on the stepsize. This information is coded by the big-O

notation above. Note that the error in the Euler algorithm is proportional to h2 only

locally - this is the order of error for a single step. However, since the number of steps

increase in inverse proportion to the stepsize, the global error is actually O(h)!

Can we check out the above? Of course we can! Remember, we have actually modified

our program drag.py to tell us the error in the calculated velocities too! So, all we

have to do is to run the program for two different values of the parameter δt and see

how v − vex actually changes between the two cases.

12.0.2 The modified Euler alorithm

An improvement to the Euler algorithm is suggested immediately by the Taylor series,

carried out to the next order. Then,

f (x + h) = f (x) + hf ′ (x) +1

2f ′′ (x) h2 +

1

6f ′′′ (ξ) h3

This equation has a local error proportional to the cube of the stepsize. Our differential

equation (the one relating v to the acceleration) is first order, so how can we find the

value of f ′′ (x)) that we need to use this formula? Using first order Taylor series on the

Page 191: Programming for Physics in Python

CHAPTER 12. ... BETTER ALGORITHMS 4

derivative f ′(x) we get

f ′ (x + h) = f ′ (x) + hf ′′(x) + O(h2)

and thus

f ′′ (x) =1

h(f ′(x + h)− f ′(x)) + O (h)

Using this value above we see that

f (x + h) = f (x) + h

(f ′ (x) + f ′ (x + h)

2

)+ O

(h3)

and thus

yn+1 = yn + h

(F (xn, yn) + F (xn+1, yn+1)

2

)+ O

(h3)

(12.1)

This formula gives a local error estimate of the order of h3 - and thus the global error

is O (h2).

In our case, the result for the velocity will be

v (t + δt) = v (t) + δt×(

a (t) + a (t + δt)

2

)(12.2)

This result is eminently reasonable. Since the acceleration varies within the interval δt,

it stands to reason that using the mean of the accelerations at the beginning and the

end of the interval should improve accuracy over the use of just the acceleration at the

beginning.

The trouble is, how can we calculate the acceleration at the end of the interval? In

most cases, one will have to know the position and the velocity at any instant to know

the acceleration, but according to the formula we will have to know the acceleration at

the end of the interval to know the velocity then! The modified Euler algorithm works

around this problem by “predicting” a value of v (t + δt) (and, if necessary, x (t + δt)

also) by using the simple Euler algorithm, using this to calculate the acceleration at the

end of the interval and then using (12.2) to calculate the “corrected” value of v (t + δt).

Page 192: Programming for Physics in Python

CHAPTER 12. ... BETTER ALGORITHMS 5

This gives rise to the other name for this method - the Euler predictor-corrector method.

Once the algorithm for the method is clear, coding it in a python program is almost

trivial. Note that the advantage of modularizing our program, i.e. breaking it up into

functions, is already paying off - all we have to do is change the update function, and

we can use the new method!

#function that implements the Euler predictor -

corrector algorithm

def update(v,x,t):

a = force(v,x,t)/m

vn = v + a*deltat

xn = x + v*deltat

t = t + deltat

an = force(vn,xn,t)/m

vnn = v + (a + an)*deltat/2.0

xnn = x + (v + vnn)*deltat/2.0

return vnn, xnn, t

Run the new program with the same values of δt as before. Compare the two errors

and see how much of an improvement the new method is!

12.0.3 The Runge - Kutta methods

The modified Euler method is certainly an improvement over the original Euler method -

but it is still a long way off from being a truly effective numerical method. Two German

mathematicians Runge and Kutta developed an algorithm that is both efficient as well

as rather accurate. We will be concerned mostly with the fourth and fifth order Runge-

Kutta method, although higher order methods can be devised too. The basic idea,

though, is similar to the algorithms above - matching the first few terms of the Taylor

series as an approximation for the new function. Indeed, as we will see, the Modified

Euler algorithm is actually a second order Runge-Kutta method.

Page 193: Programming for Physics in Python

CHAPTER 12. ... BETTER ALGORITHMS 6

Since the algebra involved in deriving the higher order Runge-Kutta algorithms is rather

tedious, let me illustrate the method by considering the second order algorithm. If you

are interested in the final result and not in the derivation, feel free to skip ahead - you

will not be missing much! In this method we essentially find the value of yn+1 by adding

to yn a weighted average of two estimates of the increment

yn+1 = yn + αk1 + βk2

where the first increment k1 is taken to be the Euler estimate hF (xn, yn) and the second,

k2 is h times the value of the slope at some point (xn + ah, yn + bk1) within the interval

(xn, xn + h)× (yn, yn + k1).

k1 = hF (xn, yn)

k2 = hF (xn + ah, yn + bk1)

Our aim is to choose the parameters a, b, α and β in such a way that our expression for

yn+1 matches the second order Taylor expansion

yn+1 = yn + hF (xn, yn) +h2

2× dF

dx(xn, yn) + O

(h3)

at least upto the second order in h. Note that

dF

dx(xn, yn) =

∂F

∂x(xn, yn) +

∂F

∂y(xn, yn)× dy

dx= (Fx + FyF )n

in an obvious shorthand. So, the Taylor expansion can be written as

yn+1 = yn + hFn +h2

2(Fx + FyF )n + O

(h3)

On the other hand, the bsecond increment k2 can be expanded as

k2 = hF (xn + ah, yn + bk1)

= h(F + ahFx + bk1Fy + O

(h2))

n

Page 194: Programming for Physics in Python

CHAPTER 12. ... BETTER ALGORITHMS 7

= h (F + ahFx + bhFFy)n + O(h3)

Thus the Runge Kutta second order form, upto the second order in h is just

yn+1 = yn + (a + b) hFn + h2 (αbFx + βbFyF )n + O(h3)

If this is to agree with the Taylor series expansion for an arbitrary function F (x, y), we

must have

a + b = 1, αb =1

2, βb =

1

2

So, demanding that we get the same expansion as the second order Taylor series gives

us three equations for the four unknowns. So, we are free to choose any one at will.

One choice that satisfies the given equations is

a = b =1

2, α = β = 1

You should check that these values gives rise to the same expression for yn+1 as the

modified Euler method of the last section.

The most widely used Runge Kutta method is the fourth order Runge Kutta. It is

derived in a similar fashion as the above, but this time you land up with 11 equations

in 13 unknowns, with two arbitrary choices. The most commonly used fourth order

Runge Kutta method takes the form

yn+1 = yn +1

6(k1 + 2k2 + 2k3 + k4)

k1 = hF (xn, yn)

k2 = hF

(xn +

1

2h, yn +

1

2k1

)k3 = hF

(xn +

1

2h, yn +

1

2k2

)k4 = hF (xn + h, yn + k3)

Page 195: Programming for Physics in Python

CHAPTER 12. ... BETTER ALGORITHMS 8

12.0.4 Taking the earths spin into account

We can add more and more twists to the projectile example - taking it closer to reality.

For example, we may try to model the effect of spin of the projectile (this is the Magnus

force, well known to all followers of ball games as the cause of swing). Another effect,

that we will talk about in this section, is that due to the rotation of the earth. The fact

that the earth rotates about the axis joining the two poles with an angular velocity of

2π radians per day, or 7.27× 10−5 rad s−1 means that the earth based observer is not

inertial. This leads to an extra pseudoforce called the Coriolis force to come into play.

This force is given by −2m~ω × ~v where ~ω is the angular velocity of the earth.

Page 196: Programming for Physics in Python

Index

algorithm, 2

assignment, 8

block, 12

break, 19

comments, 6

complex, 10

compound statement, 12

conditional statement, 10

constant, 11

drag.py, 5

equality, 9

falling particle, 1

float, 10

for, 10

format operator, 3

header, 12

indentation, 12

integer, 10

len(), 10

list, 9

loop, 14

ODE, 2

operator overlaying, 3

pass, 13

random walk, 6

semantic error, 16

statements, 7

string, 10, 18

tuple, 10

type, 10

variables, 7

while, 14

while ... else, 8

9