Вы находитесь на странице: 1из 196

Programming for Physics

in Python
Ananda Dasgupta
Department of Physics,
St. Xaviers College
Kolkata
Version 0.5

Contents
1 The basics

1.1

Why study programming? . . . . . . . . . . . . . . . . . . . . . . . . .

1.2

What is Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.3

Why learn Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1.4

Example program - Hello world . . . . . . . . . . . . . . . . . . . . .

2 Dynamics on the computer

2.1

A falling particle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2.2

Algorithm for the falling body problem . . . . . . . . . . . . . . . . . .

2.3

The program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2.4

Explanation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

2.5

Using the program . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

18

2.6

Fitting the data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

21

3 Investigating oscillations

3.1

The simple harmonic oscillator . . . . . . . . . . . . . . . . . . . . . . .

3.2

Roughing up the oscillator . . . . . . . . . . . . . . . . . . . . . . . . .

3.3

Whats the envelope? . . . . . . . . . . . . . . . . . . . . . . . . . . . .

CONTENTS

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

4.1

The math module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4.2

Defining your own functions . . . . . . . . . . . . . . . . . . . . . . . .

4.3

Asking the user . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

14

4.4

Handling files in python . . . . . . . . . . . . . . . . . . . . . . . . . .

16

5 Root finding

5.1

Warmup - the quadratic equation . . . . . . . . . . . . . . . . . . . . .

5.2

Bracketing roots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5.3

The bisection method . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5.4

Evaluating polynomials better . . . . . . . . . . . . . . . . . . . . . . .

5.5

The regula falsi method . . . . . . . . . . . . . . . . . . . . . . . . . .

12

6 The power of iterations

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

. . . . .

CONTENTS

7 Reading between the lines - interpolation

7.1

Newton interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7.2

Lagrange interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . .

11

7.3

Spline interpolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

13

8 Simulating random processes

8.1 Measurement of . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8.2

Radioactive decay . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

8.3

The random walk problem . . . . . . . . . . . . . . . . . . . . . . . . .

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

9.1

Handling files II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9.2

Improving the input routine . . . . . . . . . . . . . . . . . . . . . . . .

9.2.1

The raw input() function . . . . . . . . . . . . . . . . . . . . . .

9.2.2

Default and named arguments . . . . . . . . . . . . . . . . . . .

9.2.3

String concatanation . . . . . . . . . . . . . . . . . . . . . . . .

9.2.4

A few more improvements . . . . . . . . . . . . . . . . . . . . .

10

9.2.5

Excception handling in python . . . . . . . . . . . . . . . . . . .

11

9.2.6

Documentation strings . . . . . . . . . . . . . . . . . . . . . . .

14

Your own module(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . .

15

9.3.1

Storing your modules . . . . . . . . . . . . . . . . . . . . . . . .

16

9.3.2

Testing your modules . . . . . . . . . . . . . . . . . . . . . . . .

18

9.3

10 Getting more ambitious - dynamics part II


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

1
1

CONTENTS

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

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

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

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

11 A touch of class
11.1 Barebones classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12 Raising the accuracy - better algorithms

1
1
1

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

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

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

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

Chapter 1
The basics
1.1

Why study programming?

Lets 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.
1

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

CHAPTER 1. THE BASICS

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 its documentation 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 elses program (and
even your own!).
Object oriented as well as structured programming can be carried out with minimal 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

CHAPTER 1. THE BASICS

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
2

Note that the $ sign above is meant to stand for the prompt that the operating shell is going to
provide you with. Your prompt will vary, depending on how your system is set up. For example, in
our systems, if you log in to the machine zeews as the user gauss, and have moved to the student
directory 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 > > >.

CHAPTER 1. THE BASICS

$ ./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 interpreters prompt thats asking you for a command. 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

CHAPTER 1. THE BASICS


#!

/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.

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 Newtons second law yields
m

dv
= mg kv
dt

(2.1)

For a particle falling from rest, the solution is



mg 
kt
m
v=
1e
k

(2.2)

CHAPTER 2. DYNAMICS ON THE COMPUTER

Another integration tells us that the displacement is given by


x=

i
kt
mg 
mh
t
1 e m
k
k

(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 dont 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
thats 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

CHAPTER 2. DYNAMICS ON THE COMPUTER

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 assumptions 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.

CHAPTER 2. DYNAMICS ON THE COMPUTER

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

CHAPTER 2. DYNAMICS ON THE COMPUTER

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

CHAPTER 2. DYNAMICS ON THE COMPUTER

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 lets take a step by step look at its
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 completely 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

CHAPTER 2. DYNAMICS ON THE COMPUTER

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 - its the programs 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
1

My computer has the python interpreter in the directory /usr/bin - yours may well differ! If you
are in a Linux environment type the command
which python
to find out!

CHAPTER 2. DYNAMICS ON THE COMPUTER


and
assert
break
class

continue
def
del
elif

else
except
exec
finally

for
from
global
if

import
in
is
lambda

8
not
or
pass
print

raise
return
try
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 computers 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 wouldnt mass be better? The underscore sign
is allowed in a variable name, but dont 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. Whats this big deal between equality and assignment? Well a little later we
meet the line

CHAPTER 2. DYNAMICS ON THE COMPUTER

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 v 2 .
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 == (thats 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
2

Warning : 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 - heres 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 new
location, and not the old one. This is something that happens every time you assign a variable to
something new - even an entirely different type of quantity. Those of you with some programming
background (especially in languages like C) might be worried about the possibility of a memory leak
here - 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, rather
appropriately, garbage collection) which automatically checks for memory locations that are no longer
in use and hands them back to the pool of available and ready for use memory locations - thereby
avoiding nightmarish scenarios (which are, unfortunately, all too plausible, and common, if you are
using C and are not very careful) where a program keeps on blocking memory until the computer
refuses to run on what little is left!

CHAPTER 2. DYNAMICS ON THE COMPUTER

10

is an example of a conditional statement. Other examples are


a
a
a
a

< b
<= b
> b
>= 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 - its 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 wouldnt 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)
3

If you dont know this already, the gcc (or GNU C) compiler is the standard open source C
compiler 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 form
the error message will take depends strongly on the particular compiler you use - although the content
will be (at least roughly) the same!

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 (thats 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

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
its 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,

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 computers memory limits this to some astronomically big value - but dont 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

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!
Lets 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

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 cant 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

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 wont 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

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!

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, lets make use of a rather simple device that the Linux operating system gives
us, just change the command calling the interpreter to

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 isnt 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 immediately 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 its plots directly to the screen, so that you can see them. Thats 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

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, t
vs. 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 - its 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
dont 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

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 its 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
4

The 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.

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.

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

CHAPTER 3. INVESTIGATING OSCILLATIONS

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

CHAPTER 3. INVESTIGATING OSCILLATIONS

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
1
1
E = mv 2 + kx2
2
2

CHAPTER 3. INVESTIGATING OSCILLATIONS

is a constant, which is precisely the equation of an ellipse! This plot is called the phaseplot - 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


F
d2 x
m 2 = kx + F = k x
dt
k
shows that the quantity x Fk satisfies 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

CHAPTER 3. INVESTIGATING OSCILLATIONS

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, its 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
kx + mg

if
if

v>0
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 :

CHAPTER 3. INVESTIGATING OSCILLATIONS

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

CHAPTER 3. INVESTIGATING OSCILLATIONS

if (v>0):
a = (-k*x-mu*m*g)/m
else :
a = (-k*x+mu*m*g)/m
Note the indentation (and dont 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 its 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

Whats 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

CHAPTER 3. INVESTIGATING OSCILLATIONS

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? Lets 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
1

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

CHAPTER 3. INVESTIGATING OSCILLATIONS

(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

CHAPTER 3. INVESTIGATING OSCILLATIONS

(a)

10

(b)

Figure 3.4: The velocity envelope where the magnitudes of the extrme values of the
velocity 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

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 gnuplots
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

CHAPTER 3. INVESTIGATING OSCILLATIONS

(a)

12

(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
a line. This will only give you the lower half of the evnvelope, though
why?). To get the upper half, we will have to ask gnuplot to plot out
point, but starting with the second point in the file! To do this, what we

file joined by
(can you see
every second
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 dont 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.Thats
because green is the colour of the second linestyle that gnuplot has - and by default

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 = 21 kA2 , 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

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

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 positionq
but is,
rather, a point of unstable equilibrium. It is easy to check that the points x = k are
the actual positions of stable equilibrium in this case. What difference will this make

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 centeredqat a point
displaced to the right. This, of course is the stable equilibrium position + k for this
force. There should q
of course be similar ellipses centered around the other point of
stable equilibrium, k - 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

CHAPTER 3. INVESTIGATING OSCILLATIONS

17

Figure 3.8: Detailed phase plots of the double well potential. (a) Integration carried
out up to only t = 6.3 - which shows several incomplete curves. (b) Integrating for
longer times gives us the complete phase curves.
itself clearly from the phase plot. Whats perhaps even more apparent is the distortion
in the phase plot as the amplitude gets progressively larger. Something rather
q dramatic

can be seen when the initial value of x crosses 10 (this is the value of 2k
) . The

q
curves which were bounded around the point + k before this (though not symmetrical
around this point) suddenly change to curves which are centered around the origin now.
This is easily
q 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

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.
Noteq
that the red curves represent bound motion around the stable equilibrium position
at + k , while the green curves represent motion around the other stable equilibrium
q
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

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

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
1.5
2.0
2.5
3.0
3.5
4.0
4.5

6.282
6.282
6.282
6.282
6.282
6.282
6.282
6.282

2.00000012072
3.00000018108
4.00000024144
5.0000003018
6.00000036216
7.00000042252
8.00000048288
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
2

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

CHAPTER 3. INVESTIGATING OSCILLATIONS

21

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

divergence in the time period as the initial position approaches


acquires enough energy to just cross the barrier.

2k

where the particle

expectation that the time period for the double


q 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
s
T = 2

l
g

CHAPTER 3. INVESTIGATING OSCILLATIONS

(a)

22

(b)

Figure 3.10: Dependence of the time period of oscillation on the energy for (a) the
nonlinear oscillator with restoring force kx x3 and (b) the double well potential
with 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
+ sin = 0
l
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

CHAPTER 3. INVESTIGATING OSCILLATIONS

23

Figure 3.11: Simple pendulum - (a) phase plots and (b) variation of time period with
amplitude
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 *
dont 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

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 - thats nearly 22 ! So, it doesnt 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. Thats 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
d2 x
(3.1)
m 2 = kx v + F0 sin (t)
dt
The reason why we give this case so much importance ultimately lies at the door
of a theorem in mathematics called Fouriers theorem - which says, roughly, that

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 oscillator (a) x vs. t for = 0.30 , (b) v vs. t for = 0.30 , (c) x vs. t for = 0.80 ,
(d) v vs. t for = 0.80 . Note the increase in the amplitudes as the driving frequency
approaches 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

CHAPTER 3. INVESTIGATING OSCILLATIONS

26

with t for a forced harmonic oscillator where = 0.30 . 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.80 . 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.80 with different initial conditions - x = 0


and v = 0 at t = 0. (b) Shows the same graph superposed with that for the previous
initial 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

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

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

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.

CHAPTER 3. INVESTIGATING OSCILLATIONS

(a)

30

(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 whereas
amplitude resonance occurs at a slightly lower frequency. This difference, however, is
hardly noticable in the graph above - but it rises with increasing damping. The green
lines 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

CHAPTER 3. INVESTIGATING OSCILLATIONS

31

Figure 3.15: Resonance curves for a damped harmonic oscillator. Note that the curves
become progressively smaller in height and flatter as the damping rises.

CHAPTER 3. INVESTIGATING OSCILLATIONS

32

velocity with frequency is given by


vmax = r

m0

2

F0
2 m0

(3.2)

Which clearly shows the maximum at = 0 . Also, the peak value is seen to be
which is why the curves diminished in height as the damping increased.

3.9

F0

Forced vibrations without damping and nonlinearity

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.80 and (b) with = 0.950 . Note the beats that occur between the
undamped natural oscillations at frequency 0 and the driven oscillation at frequency
.

CHAPTER 3. INVESTIGATING OSCILLATIONS

(a)

33

(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.80 and
= 0.950 . 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 ! Thats nonsense, of course! So, what
does really happen? Lets 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

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
m

dx
d2 x
+
+ k0 (1 + h cos (t)) x = 0
2
dt
dt

(3.3)

CHAPTER 3. INVESTIGATING OSCILLATIONS

35

When the point of suspension of a pendulum is externally driven to vibrate at a frequency 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
q
the phenomenon of parametric resonance. In particular when = 20 where 0 = km0
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 .

Chapter 4
More Python - improving the
dynamics program
Now that we have used the original drag.py program and its 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

CHAPTER 4. MORE PYTHON ...

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 arguments, 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 submitting 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?

CHAPTER 4. MORE PYTHON ...

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

CHAPTER 4. MORE PYTHON ...

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 contains the help function while calling it. The answer is that python has a module called

CHAPTER 4. MORE PYTHON ...

__builtin__ (Note the name carefully, thats two underscore characters each at the
front and the back of the name!), and all functions belonging to that module are immediately 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 elses 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. Thats 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:

CHAPTER 4. MORE PYTHON ...


Help on module math:
NAME
math
FILE
/usr/local/lib/python2.3/lib-dynload/math.so
DESCRIPTION
This module is always available. It provides access 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.

CHAPTER 4. MORE PYTHON ...

DATA
e = 2.7182818284590451
pi = 3.1415926535897931
You dont 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

CHAPTER 4. MORE PYTHON ...

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

CHAPTER 4. MORE PYTHON ...

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

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,
lets 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

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, lets 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 :
1

This may let you wonder how you can get the simple task of swapping two variables done at all
by means of a function. Once you learn a bit more about namespaces, and the details of how python
handles 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 you
have to do is
x, y = y, x
- try it for yourself and check!

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 - dont 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! Thats 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?

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

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 pythons 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 wouldnt bet on even that!) - but what if someone

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 :

CHAPTER 4. MORE PYTHON ...

16

gnuplot> plot drag.py


^
Bad data on line 1
thats 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

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

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 dont have any arguments to pass and so the () is empty!

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=

b2 4ac
2a

CHAPTER 5. ROOT FINDING

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)

CHAPTER 5. ROOT FINDING

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.

CHAPTER 5. ROOT FINDING

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 pythons, 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

CHAPTER 5. ROOT FINDING

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 considerably 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

CHAPTER 5. ROOT FINDING

using gnuplot to locate the roots is cheating - cant 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
. Now the
this, we start with the initial bracketing interval (a, b) and bisect it at c = a+b
2
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 -

CHAPTER 5. ROOT FINDING

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

CHAPTER 5. ROOT FINDING

a=c
else:
print The bisection 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: construct. 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.

CHAPTER 5. ROOT FINDING

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, lets 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
>>>
>>>
1
>>>
-6
>>>
7
>>>
4

li = [1,-6,5,7]
li[0]
li[1]
li[-1]
len(li)

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.

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
user
sys

0m0.058s
0m0.010s
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
user
sys

0m0.035s
0m0.030s
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 !
1

Allow me to quote the much used book Numerical Recipes by W. Press et al in this context

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 evershrinking 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 behavior
will be summarily executed, and their programs wont be!

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 dont 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)
(x a)
ab

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+b
in the bisection method). If
2
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

CHAPTER 5. ROOT FINDING

14

Figure 5.2: The regula falsi method. We start with the root brackected between the
points marked 1 and 2. The straight line joining these points gives us the new endpoint
mrked 3. Since in this case, the root is between 1 and 3, we continue with that interval
getting the point 4 and so on ...

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.

CHAPTER 5. ROOT FINDING

16

Figure 5.3: An example of a function where regula falsi method will take a long time
to converge to the root. The bisection method will actually work better in this case!

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 =

1
xn
+
2
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,
1 1
3
+ =
2 1
2
1.5
1
=
+
= 1.41667
2
1.5
= ...

x1 =
x2
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

CHAPTER 6. THE POWER OF ITERATIONS

...
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
1
+ = 2!
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
1

Actually it is very easy to find the fixed point of the iterations that we are looking at - all we have
to do is to realise that if x is a fixed point, then the iterate that you will get starting from x is also

CHAPTER 6. THE POWER OF ITERATIONS

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
2
3
4
5

500.001
250.002499996
125.005249958
62.510624643
31.2713096021

This doesnt 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
1
+
2
x

2
which simplifies
to (x ) = 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.
2
If 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 can
run the iterations again.

CHAPTER 6. THE POWER OF ITERATIONS

for i in [0,1,2,3,4,5,6,7,8,9]:
and the result is
1
2
3
4
5
6
7
8
9
10

500.001
250.002499996
125.005249958
62.510624643
31.2713096021
15.6676329949
7.89764234786
4.07544124052
2.28309282439
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? Whats 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]

CHAPTER 6. THE POWER OF ITERATIONS

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 function range:
range([start,] stop[, step]) -> list of integers
turn a list containing an arithmetic progression of integers. range(i, j) returns [i, i+1, i+2, ..., j1]; start (!) defaults to 0.
When step is given, it specifies the increment (or decrement). For example, range(4) returns [0, 1, 2, 3]. The end point is omitted! These are exactly 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 :

range(...)

CHAPTER 6. THE POWER OF ITERATIONS

> > > 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, seventh]
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

CHAPTER 6. THE POWER OF ITERATIONS

Storing this in a file sqrt.py and then typing


$ python sqrt.py
gives the output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

500.001
250.002499996
125.005249958
62.510624643
31.2713096021
15.6676329949
7.89764234786
4.07544124052
2.28309282439
1.57954875241
1.42286657958
1.41423987359
1.41421356262
1.41421356237
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 x1 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

CHAPTER 6. THE POWER OF ITERATIONS

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):

CHAPTER 6. THE POWER OF ITERATIONS

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)) ?

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 dont 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.

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 .
Lets 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
theres 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

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
p n = n p 0
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
3

Nearer home, you may know that the mango production in the district of Malda shows the same
sort of variation.

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 41 (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

Lets 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

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

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

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

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 spiders 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)

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

CHAPTER 6. THE POWER OF ITERATIONS

18

Figure 6.1: Cobweb diagrams for various different values of . (a) = 2.9 - note the
1
, (b) = 3.1 - note the period two cycle, (c)
convergence to the fixed point at 1 2.9
= 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

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

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

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.

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 NewtonRaphson 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

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 0 (), it intersects the X axis at the point
with ordinate x given by
f () 0
f 0 () =
x
so that
x=

f ()
f 0 ()

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
f (xn )
(6.3)
xn+1 = xn 0
f (xn )
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
4

Another way to look at this is by the means of the Taylor expansion


1
f ( + ) = f () + f 0 () + f 00 () 2 + . . .
2

If you ignore quadratic or higher order terms in , the equation f ( + ) = 0 is satisfied by ff0()
() .

CHAPTER 6. THE POWER OF ITERATIONS


we have
f 0 (x) = 3x2 12x + 5
and thus the NR iteration scheme becomes
x3n 6x2n + 5xn + 7
3x2n 12xn + 5
3
2xn 6x2n 7
=
3x2n 12xn + 5

xn+1 = xn

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 converge in ,maxSteps, steps
Running this program produces the output
$ python NRiter.py

24

CHAPTER 6. THE POWER OF ITERATIONS


The NRiteration converged in 5
The root is
2.14327732184

25

steps.

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

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

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.

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 ) . . . , (xn1 , yn1 )
- 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

CHAPTER 7. ... INTERPOLATION

Figure 7.1: The non-unique nature of interpolating curves - the black circles represent
data 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

CHAPTER 7. ... INTERPOLATION

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 , . . . , xn1 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) =

y (x + h) y (x)
dy
= lim
dx h0
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

CHAPTER 7. ... INTERPOLATION


i yi yi
0 12 -4
1 8
-3
2 5 +4
3 9 +2
4 11 -4
5 7
-

2 yi
+1
+7
-2
-6
-

4
3 yi
+6
-9
-4
-

4 yi
-15
+5
-

5 yi
+20

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
2 yi = (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 , . . . , yn1 we can calculate n 1
first differences y0 , y1 , . . . , yn1 . Applying the difference opeator once again, we
can get n2 second differences 2 y0 , 2 y1 , . . . , 2 yn2 . This can be continued over and
over, at most n 1 times overall when we land up with the single n 1 order difference
n1 y0 . 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
1

If this seems odd to you, welcome to the club! After all, the difference operator is way more
elementary compared to the derivative operator - it just talks of subtraction, instead of complicated
things such as taking limits. It is only that we are more used to seeing the difference operator - and
so we think of the difference operator as a version of it!

CHAPTER 7. ... INTERPOLATION

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) =

n1
X

am x(m)

m=0

and our job here is to find the n coefficients a0 , a1 , . . . , an1 .


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(m1)

CHAPTER 7. ... INTERPOLATION

and hence we have


l x(m) = m (m 1) . . . (m l + 1) x(ml)

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
1 m
am =
y0
m!
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) 6 (3) 15 (4) 20 (5)


x + x x + x
2!
3!
4!
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])

CHAPTER 7. ... INTERPOLATION

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 , 2 y1 . . . , n1 y0 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

CHAPTER 7. ... INTERPOLATION

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,
isnt 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

CHAPTER 7. ... INTERPOLATION

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)

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

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? Dont 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 interpolated 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 xi1 ) (x xi+1 ) . . . (x xn1 )


(xi x0 ) (xi x1 ) . . . (xi xi1 ) (xi xi+1 ) . . . (xi xn1 )

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 moments thought will tell you that this means that the colocation polynomial that
we want is given simply by
n1
X
f (x) =
yi Li (x)
i=0

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

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. Its 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

7.3

is

10.8515625

Spline interpolation

Useful as the above two interpolation methods are - they do suffer from one major
weakness. Let me illustrate this with an example.

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

. So, if one were to do this experiment, the resulting fraction will give us an estimate
4
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

CHAPTER 8. SIMULATING RANDOM PROCESSES

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 + y 2 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

CHAPTER 8. SIMULATING RANDOM PROCESSES

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 N dt where N is the number of nuclei
present at that time. So, the number of nuclei obeys the equation
dN = N dt
and so we arrive at the familiar equation for radioactive decay
dN
= N
dt
which has the solution
N = N0 et
- the familiar exponential decay law.

CHAPTER 8. SIMULATING RANDOM PROCESSES

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

CHAPTER 8. SIMULATING RANDOM PROCESSES

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.

CHAPTER 8. SIMULATING RANDOM PROCESSES

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, N CN+ 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+ q N , 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!
p N+ q N
(N+ )! (N )!

or, in terms of the final displacement,


N
N!
PN (d) = N +d  N d  (pq) 2
! 2 !
2

  d2
p
q

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

CHAPTER 8. SIMULATING RANDOM PROCESSES

choose to ignore the fact that these two add up to 1. This defines the function
f (p, q) =

N
X

PN (N+ ) =

N+ =0

N
X
N+

N!
pN+ q N = (p + q)N
(N
)!
(N
)!
+

=0

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
hN+ i =

N
X

N+ PN (N+ )

N+ =0




= p f (p, q)
p
p+q=1
= N p (p + q)N 1 = N p
whereas the mean-squared value can be calculated from
hN+ (N+ 1)i =

N
X

N+ (N+ 1) PN (N+ )

N+ =0



2
= p
f (p, q)
p2
p+q=1
= N (N 1) p2 (p + q)N 1 = N (N 1) p2
so that


N+2 = N (N 1) p2 + N p

The above manipulations should make it clear why I had chosen to define the function f (p, q). Now, it should be pretty easy to calculate the mean and mean-squared
displacement
hdi = h2N+ N i = 2 hN+ i N = (2p 1) N

d
= 4N+2 4N+ N + N 2 = 4 N+2 4N hN+ i + N 2
= 4N (N 1) p2 + 4N p 4N 2 p + N 2

CHAPTER 8. SIMULATING RANDOM PROCESSES

= 4N p (1 p) + [(2p 1) N ]2
This means that


(d)2 = d2 hdi2 = 4N p (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 meansquared 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 hdi and hd2 i 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 : )

CHAPTER 8. SIMULATING RANDOM PROCESSES

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

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 calculations 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 hd2 i = 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
(x x)2
1
exp
P (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


1
d hdi
PN (d) =
exp
2 (d)
2 (d)2
Using the built in fitting programs available in gnuplot, we can try to fit a Gaussian to

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>
gnuplot>
gnuplot>
gnuplot>
gnuplot>

f(x) = a * exp(-(x-b)**2/c)
a = 0.1
b = 0
c = 200
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
1

Actually the reason that I decided to omit the b from the parameter list of via is that I am
reasonably 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 to
consider the possibilty that my random number generator is not really working as it should!

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.

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 dont
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

CHAPTER 9. EVEN MORE PYTHON ...

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) -

CHAPTER 9. EVEN MORE PYTHON ...

and the order of these numbers matter (you wouldnt 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
1

Remember - when you refer to a you are talking about a string which has the single character
a in it, when you refer to a you are referring to that variable a!

CHAPTER 9. EVEN MORE PYTHON ...

- 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()

CHAPTER 9. EVEN MORE PYTHON ...

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 : )

CHAPTER 9. EVEN MORE PYTHON ...

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(#
f.write(#
f.write(#
f.write(#
f.write(#

Output of the program drag.py \n\n)


Values of the parameters used :\n)
m = %-10.3f g = %-10.3f k = %.3f \n\n % (m,g,k))
Initial conditions at t = %.3f :\n % ti)
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
9.2.1

Improving the input routine


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

CHAPTER 9. EVEN MORE PYTHON ...

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

CHAPTER 9. EVEN MORE PYTHON ...

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, doesnt 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!

CHAPTER 9. EVEN MORE PYTHON ...

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().

CHAPTER 9. EVEN MORE PYTHON ...

9.2.4

10

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() function. Takes two arguments, the prompt and the default
value. Returns a float. Keeps on repeating until valid input is provided """
notDoneYet =1
if default == ":
newprompt = prompt
else :
newprompt = prompt + ( + str(default) + )
while notDoneYet:
notDoneYet = 0

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-

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 cryptically, 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.

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. Dnt 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 dont 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

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

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

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 functions 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

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>

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
dont 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

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, thats 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.

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 - dont 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

CHAPTER 10. GETTING MORE AMBITIOUS ...

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 under 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

CHAPTER 10. GETTING MORE AMBITIOUS ...

#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 coefficient = %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 attained is %f and the time of flight is%f\n%(x,height,t))
f.write(#Ideally these should have been %f, %f and %f respectively \n % (idealRange,idealHeight,idealTimeOfFlight))
f.close()

CHAPTER 10. GETTING MORE AMBITIOUS ...

Figure 10.1: The path of a projectile. The outer blue line corresponds to the case where
drag is absent. The inner red line is obtained with a drag coefficient of k = 0.05 Kg s1
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.

CHAPTER 10. GETTING MORE AMBITIOUS ...

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 x2 satisfy the differential equations
d2 x1
= k (x2 2x1 )
dt2
d2 x2
m 2 = k (x1 2x2 )
dt

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 x1 and 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

CHAPTER 10. GETTING MORE AMBITIOUS ...

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 conditions mentioned in the text. Note the simple harmonic nature of the displacements for
the 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

CHAPTER 10. GETTING MORE AMBITIOUS ...

(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
1

Actually, it is three, but angular momentum conservation confines the motion to a plane.

CHAPTER 10. GETTING MORE AMBITIOUS ...

Note that we have nested the function r(x1,x2) inside force(x1,x2). This makes
p
sense, because the only part of the program where r =
x21 + x22 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 Newtons theory of gravitation, though immensely succesful,
is only an approximation! A more precise theory is supplied by Einsteins general
theory of relativity. The reason why no one had found any fault with Newtons theory
for three hundred years, of course, is that the corrections that GTR introduces over
Newtons 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 Mercurys orbit will shift by about 4300 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.
2

Since Mercury is the closest to the sun, and the fastest among all the planets, the correction that
GTR induces for Mercury is actually the largest for all the planets!

CHAPTER 10. GETTING MORE AMBITIOUS ...

10.4

The Lorentz butterfly

So far, we have been talking about moving bodies. However, note that the same algorithm 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 parameters 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.

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
- thats 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

CHAPTER 11. A TOUCH OF CLASS

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 isnt 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

CHAPTER 11. A TOUCH OF CLASS

> > > 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__
1

Actually, these are not very disparate example. Although you could program in python without
ever bothering about objects - everything in python are actually objects! So the math module as well
as the f file handle are both objects - and hence the notation is actually uniform - you first name the
object the variable or procedure belongs to and follow it up with the name of the variable or procedure
itself - with a dot in the middle.

CHAPTER 11. A TOUCH OF CLASS

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 guaranteed to be unique among simultaneously existing objects. (Hint: its the objects 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

CHAPTER 11. A TOUCH OF CLASS

> > > 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):

CHAPTER 11. A TOUCH OF CLASS

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, display, frame, green, name, pos, radius, red, rotate, up, visible, x, y, z]
what the interpreter does is give you a list of attributes that the object a has.

Chapter 12
Raising the accuracy - better
algorithms
As we have seen in the last few sections, the Euler algorithm is adequate for most purposes, 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 interval 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

CHAPTER 12. ... BETTER ALGORITHMS

> > > 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
1
f (x + h) = f (x) + hf 0 (x) + h2 f 00 ()
2

CHAPTER 12. ... BETTER ALGORITHMS

= f (x) + hf 0 (x) + O h2

where is a number in the interval (x, x + h). So if we are solving an eqation of the
form
dy
= F (x, y)
dx
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,
1
1
f (x + h) = f (x) + hf 0 (x) + f 00 (x) h2 + f 000 () h3
2
6
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 00 (x)) that we need to use this formula? Using first order Taylor series on the

CHAPTER 12. ... BETTER ALGORITHMS

derivative f 0 (x) we get


f 0 (x + h) = f 0 (x) + hf 00 (x) + O h2

and thus

1 0
(f (x + h) f 0 (x)) + O (h)
h
Using this value above we see that
f 00 (x) =


f (x + h) = f (x) + h
and thus


yn+1 = yn + h

f 0 (x) + f 0 (x + h)
2

F (xn , yn ) + F (xn+1 , yn+1 )


2

+ O h3

+ 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).

CHAPTER 12. ... BETTER ALGORITHMS

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 RungeKutta 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.

CHAPTER 12. ... BETTER ALGORITHMS

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 dF

(xn , yn ) + O h3
2
dx

at least upto the second order in h. Note that


F
F
dy
dF
(xn , yn ) =
(xn , yn ) +
(xn , yn )
= (Fx + Fy F )n
dx
x
y
dx
in an obvious shorthand. So, the Taylor expansion can be written as
yn+1 = yn + hFn +


h2
(Fx + Fy F )n + O h3
2

On the other hand, the bsecond increment k2 can be expanded as


k2 = hF (xn + ah, yn + bk1 )
= h F + ahFx + bk1 Fy + O h2


n

CHAPTER 12. ... BETTER ALGORITHMS

= h (F + ahFx + bhF Fy )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 + bFy F )n + O h3

If this is to agree with the Taylor series expansion for an arbitrary function F (x, y), we
must have
1
1
a + b = 1,
b = ,
b =
2
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
1
a=b= ,
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
1
(k1 + 2k2 + 2k3 + k4 )
6
(xn , yn )


1
1
xn + h, yn + k1
2
2


1
1
xn + h, yn + k2
2
2
(xn + h, yn + k3 )

yn+1 = yn +
k1 = hF
k2 = hF
k3 = hF
k4 = hF

CHAPTER 12. ... BETTER ALGORITHMS

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 105 rad s1 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.

Index
algorithm, 2
assignment, 8

ODE, 2
operator overlaying, 3

block, 12
break, 19

pass, 13
random walk, 6

comments, 6
complex, 10
compound statement, 12
conditional statement, 10
constant, 11

semantic error, 16
statements, 7
string, 10, 18
tuple, 10
type, 10

drag.py, 5

variables, 7

equality, 9

while, 14
while ... else, 8

falling particle, 1
float, 10
for, 10
format operator, 3
header, 12
indentation, 12
integer, 10
len(), 10
list, 9
loop, 14
9

Вам также может понравиться