Programming For Computations Python
Programming For Computations Python
Programming for
Computations–
Python
Editorial Board
T. J.Barth
M.Griebel
D.E.Keyes
R.M.Nieminen
D.Roose
T.Schlick
Texts in Computational
Science and Engineering
15
Editors
Timothy J. Barth
Michael Griebel
David E. Keyes
Risto M. Nieminen
Dirk Roose
Tamar Schlick
More information about this series at http://www.springer.com/series/5151
Svein Linge Hans Petter Langtangen
Programming for
Computations – Python
A Gentle Introduction to Numerical
Simulations with Python
Svein Linge Hans Petter Langtangen
Department of Process, Energy and Simula Research Laboratory
Environmental Technology Lysaker, Norway
University College of Southeast Norway
Porsgrunn, Norway On leave from:
Department of Informatics
University of Oslo
Oslo, Norway
ISSN 1611-0994
Texts in Computational Science and Engineering
ISBN 978-3-319-32427-2 ISBN 978-3-319-32428-9 (eBook)
DOI 10.1007/978-3-319-32428-9
Springer Heidelberg Dordrecht London New York
Mathematic Subject Classification (2010): 26-01, 34A05, 34A30, 34A34, 39-01, 40-01, 65D15, 65D25,
65D30, 68-01, 68N01, 68N19, 68N30, 70-01, 92D25, 97-04, 97U50
© The Editor(s) (if applicable) and the Author(s) 2016 This book is published open access.
Open Access This book is distributed under the terms of the Creative Commons Attribution-Non
Commercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/), which permits
any noncommercial use, duplication, adaptation, distribution and reproduction in any medium or format,
as long as you give appropriate credit to the original author(s) and the source, a link is provided to the
Creative Commons license and any changes made are indicated.
The images or other third party material in this book are included in the work’s Creative Commons
license, unless indicated otherwise in the credit line; if such material is not included in the work’s
Creative Commons license and the respective action is not permitted by statutory regulation, users will
need to obtain permission from the license holder to duplicate, adapt or reproduce the material.
This work is subject to copyright. All commercial rights are reserved by the Publisher, whether the whole
or part of the material is concerned, specifically the rights of translation, reprinting, reuse of illustrations,
recitation, broadcasting, reproduction on microfilms or in any other physical way, and transmission or
information storage and retrieval, electronic adaptation, computer software, or by similar or dissimilar
methodology now known or hereafter developed.
The use of general descriptive names, registered names, trademarks, service marks, etc. in this publica
tion does not imply, even in the absence of a specific statement, that such names are exempt from the
relevant protective laws and regulations and therefore free for general use.
The publisher, the authors and the editors are safe to assume that the advice and information in this book
are believed to be true and accurate at the date of publication. Neither the publisher nor the authors or
the editors give a warranty, express or implied, with respect to the material contained herein or for any
errors or omissions that may have been made.
v
vi Preface
Target audience and background knowledge This book was written for students,
teachers, engineers and scientists that know nothing about programming and nu
merical methods from before, but who seek a minimum of the fundamental skills
required to get started with programming as a tool for solving scientific and engi
neering problems. Some knowledge of one- and multi-variable calculus is assumed.
The basic programming concepts are presented in only 50 pages (Chapters 1 and
2), before practical applications of these concepts are demonstrated in important
mathematical subjects addressed in the remaining parts of the book (Chapters 3-6).
Each chapter is followed by a set of exercises that cover a wide range of application
areas, e.g. biology, geology, statistics, physics and mathematics. The exercises were
particularly designed to bring across important points from the text. The reader will
realize that the modest content of the first 50 pages can in fact bring you quite far
in powerful problem solving!
Learning the very basics of programming should not take long, but as with any
other craft, mastering the skill requires continued and extensive practice. Some
beginning practice is gained through Chapters 3-6, but the authors strongly empha
size that this is only a start. Students should continue to practice programming in
subsequent courses, while those who exercise self-study, should keep up the learn
ing process through continued application of the craft. The book is a good starting
point when teaching computer programming as an integrated part of standard uni
versity courses in mathematics and physical sciences. In our experience, such an
integration is doable and indeed rewarding.
Numerical methods An overall goal with this book is to motivate computer pro
gramming as a very powerful tool for doing mathematics. All examples are related
to mathematics and its use in engineering and science. However, to solve math
ematical problems through computer programming, we need numerical methods.
Explaining basic numerical methods is therefore an integral part of the book. Our
choice of topics is governed by what is most needed in science and engineering, as
well as in the teaching of applied physical science courses. Mathematical models
are then central, with differential equations constituting the most frequent type of
models. Consequently, the numerical focus in this book is on differential equations.
As a soft pedagogical starter for the programming of mathematics, we have chosen
the topic of numerical integration. There is also a chapter on root finding, which
is important for the numerical solution on nonlinear differential equations. We re
mark that the book is deliberately brief on numerical methods. This is because our
focus is on implementing numerical algorithms, but to develop reliable, working
programs, the programmer must be confident about the basic ideas of the numerical
approximations involved.
The computer language: Python We have chosen to use the programming lan
guage Python, because this language gives very compact and readable code that
closely resembles the mathematical recipe for solving the problem at hand. Python
also has a gentle learning curve. There is a MATLAB/Octave companion of this
book in case that language is preferred. Comparing these two versions of the book
provides an excellent demonstration of how similar these languages are. Other
computer languages, like Fortran, C, and C++, have a strong position in science
and engineering. During the last two decades, however, there has been a significant
viii Preface
shift in popularity from these compiled languages to more high-level and easier-to
read languages like Matlab, Python, R, Maple, Mathematica, and IDL, for instance.
This latter class of languages is computationally less efficient, but superior with re
spect to overall human problem solving efficiency. This book emphasizes how to
think like a programmer, rather than focusing on technical language details. Thus,
the book should put the reader in a good position for learning other programming
languages later, including the classic ones: Fortran, C, and C++.
How this book is different There are numerous texts on computer programming
and numerical methods, so how does the present one differ from the existing lit
erature? Compared to books on numerical methods, our book has a much stronger
emphasis on the craft of programming and on verification. We want to give students
a thorough understanding of how one thinks about programming as a problem solv
ing method and how one can be sure that programs are correct (well, you can never
be completely sure, but we show how you can provide convincing evidence for
correctness).
Even though there are lots of books on numerical methods where many algo
rithms have a corresponding computer implementation (see, e.g., [1, 3–6, 11, 16–
18, 21, 24, 26–31] – the latter two are the only texts we know that apply Python),
it is assumed that the reader “can program” beforehand. The present book teaches
the craft of structured programming along with the fundamental ideas of numeri
cal methods. Furthermore, we have so far not found any other numerical methods
book that has a strong emphasis on verifying implementations. In this book, unit
testing and corresponding test functions are introduced early on. We also put much
emphasis on coding algorithms as functions, as opposed to “flat programs”, which
often dominate in the literature and among practitioners. Functions are reusable
because they utilize the general formulation of a mathematical algorithm such that
it becomes applicable to a large class of problems.
There are also numerous books on computer programming, but to our knowledge
only one [13] that aims to teach how to think about programming in the context
of numerical methods and scientific applications. That book [13] has its primary
focus on teaching Python and is a very comprehensive introduction to Python as
a language and the thinking about programming as a computer scientist. Sometimes
one needs a text that does not go so deep into the language-specific details, but
instead targets the shortest path to reliable mathematical problem solving through
programming. With this attitude in mind, a lot of topics were left out of the present
book, simply because they were not strictly needed in the mathematical problem
solving process. Examples of such topics are object-oriented programming and
Python dictionaries (of which the latter omission is possibly subject to more debate).
If you find the present book too shallow, [13] might be the right choice for you. That
source should also work nicely as a more in-depth successor of the present text.
Whenever the need for a structured introduction to programming arises in sci
ence and engineering courses, this book may be your option, either for self-study or
for use in organized teaching. The thinking, habits, and practice covered in a couple
of hundred pages will put readers in a firm position for utilizing and understanding
the power of computers for problem solving in science and engineering.
Preface ix
Supplementary materials All program and data files referred to in this book are
available from the book’s primary web site: http://hplgit.github.io/prog4comp/.
Acknowledgments First of all, we want to thank all students who attended the
courses FM1006 Modelling and simulation of dynamic systems, FM1115 Scientific
Computing, FB1012 Mathematics I and FB2112 Physics at the University College
of Southeast Norway over the last couple of years. They worked their way through
early versions of this text and gave us constructive and positive feedback that helped
us correct errors and improve the book in so many ways. Special acknowledgement
goes to Guandong Kou and Edirisinghe V. P. J. Manjula for their careful reading
of the manuscript and constructive suggestions for improvement. The careful proof
reading by Yapi Donatien Achou is also highly appreciated. We thank all our good
colleagues at the University College of Southeast Norway, University of Oslo, and
Simula Research Laboratory for their continued support and interest, enlightening
discussions, and for providing such an inspiring environment for teaching and sci
ence. In particular, Svein Linge is thankful to Marius Lysaker for their fruitful
collaboration on introducing programming as an integral part of mathematics and
physics bachelor courses at the University College of Southeast Norway. Finally,
the authors must thank the Springer team with Dr. Martin Peters, Thanh-Ha Le Thi,
and Yvonne Schlatter for the effective editorial and production process.
The text was written in the DocOnce1 [12] markup language, which allowed us
to work with a single text source for both the Python and the Matlab version of this
book, and to produce various electronic versions of the book.
1https://github.com/hplgit/doconce
Contents
xi
xii Contents
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
List of Exercises
xv
xvi List of Exercises
Exercise 3.6:
3.7: Explore
Write test
rounding
functions
errors
for Rwith
04 large numbers . . . . . . . . . . . . 89
pxdx ................... 89
Exercise 3.8: Rectangle methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Exercise 3.9: Adaptive integration . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Exercise 3.10: Integrating x raised to x . . . . . . . . . . . . . . . . . . . . . . . . 90
Exercise 3.11: Integrate products of sine functions . . . . . . . . . . . . . . . . . 91
Exercise 3.12: Revisit fit of sines to a function . . . . . . . . . . . . . . . . . . . 91
Exercise 3.13: Derive the trapezoidal rule for a double integral . . . . . . . . . 92
Exercise 3.14: Compute the area of a triangle by Monte Carlo integration . . . 92
Exercise 4.1: Geometric construction of the Forward Euler method . . . . . . . 153
Exercise 4.2: Make test functions for the Forward Euler method . . . . . . . . 153
Exercise 4.3: Implement and evaluate Heun’s method . . . . . . . . . . . . . . . 153
Exercise 4.4: Find an appropriate time step; logistic model . . . . . . . . . . . . 154
Exercise 4.5: Find an appropriate time step; SIR model . . . . . . . . . . . . . . 154
Exercise 4.6: Model an adaptive vaccination campaign . . . . . . . . . . . . . . 154
Exercise 4.7: Make a SIRV model with time-limited effect of vaccination . . . 154
Exercise 4.8: Refactor a flat program . . . . . . . . . . . . . . . . . . . . . . . . . 155
Exercise 4.9: Simulate oscillations by a general ODE solver . . . . . . . . . . . 155
Exercise 4.10: Compute the energy in oscillations . . . . . . . . . . . . . . . . . 155
Exercise 4.11: Use a Backward Euler scheme for population growth . . . . . . 156
Exercise 4.12: Use a Crank-Nicolson scheme for population growth . . . . . . 156
Exercise 4.13: Understand finite differences via Taylor series . . . . . . . . . . 157
Exercise 4.14: Use a Backward Euler scheme for oscillations . . . . . . . . . . 158
Exercise 4.15: Use Heun’s method for the SIR model . . . . . . . . . . . . . . . 159
Exercise 4.16: Use Odespy to solve a simple ODE . . . . . . . . . . . . . . . . . 159
Exercise 4.17: Set up a Backward Euler scheme for oscillations . . . . . . . . . 159
Exercise 4.18: Set up a Forward Euler scheme for nonlinear and damped
oscillations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
Exercise 4.19: Discretize an initial condition . . . . . . . . . . . . . . . . . . . . . 160
Exercise 5.1: Simulate a diffusion equation by hand . . . . . . . . . . . . . . . . 177
Exercise 5.2: Compute temperature variations in the ground . . . . . . . . . . . 178
Exercise 5.3: Compare implicit methods . . . . . . . . . . . . . . . . . . . . . . . 178
Exercise 5.4: Explore adaptive and implicit methods . . . . . . . . . . . . . . . . 179
Exercise 5.5: Investigate the  rule . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
Exercise 5.6: Compute the diffusion of a Gaussian peak . . . . . . . . . . . . . . 180
Exercise 5.7: Vectorize a function for computing the area of a polygon . . . . 181
Exercise 5.8: Explore symmetry . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
Exercise 5.9: Compute solutions as t ! 1 . . . . . . . . . . . . . . . . . . . . . 182
Exercise 5.10: Solve a two-point boundary value problem . . . . . . . . . . . . 183
Exercise 6.1: Understand why Newton’s method can fail . . . . . . . . . . . . . 206
Exercise 6.2: See if the secant method fails . . . . . . . . . . . . . . . . . . . . . 207
Exercise 6.3: Understand why the bisection method cannot fail . . . . . . . . . 207
Exercise 6.4: Combine the bisection method with Newton’s method . . . . . . 207
Exercise 6.5: Write a test function for Newton’s method . . . . . . . . . . . . . 207
Exercise 6.6: Solve nonlinear equation for a vibrating beam . . . . . . . . . . . 207
The First Few Steps
1
Today, most people are experienced with computer programs, typically programs
such as Word, Excel, PowerPoint, Internet Explorer, and Photoshop. The interaction
with such programs is usually quite simple and intuitive: you click on buttons, pull
down menus and select operations, drag visual elements into locations, and so forth.
The possible operations you can do in these programs can be combined in seemingly
an infinite number of ways, only limited by your creativity and imagination.
Nevertheless, programs often make us frustrated when they cannot do what we
wish. One typical situation might be the following. Say you have some measure
ments from a device, and the data are stored in a file with a specific format. You
© The
S. Linge,
Author(s)
H.P. Langtangen,
2016 Programming for Computations – Python, 1
may want to analyze these data in Excel and make some graphics out of it. How
ever, assume there is no menu in Excel that allows you to import data in this specific
format. Excel can work with many different data formats, but not this one. You start
searching for alternatives to Excel that can do the same and read this type of data
files. Maybe you cannot find any ready-made program directly applicable. You
have reached the point where knowing how to write programs on your own would
be of great help to you! With some programming skills, you may write your own
little program which can translate one data format to another. With that little piece
of tailored code, your data may be read and analyzed, perhaps in Excel, or perhaps
by a new program tailored to the computations that the measurement data demand.
The real power of computers can only be utilized if you can program them.
By programming you can get the computer to do (most often!) exactly what you
want. Programming consists of writing a set of instructions in a very specialized
language that has adopted words and expressions from English. Such languages
are known as programming or computer languages. The set of instructions is given
to a program which can translate the meaning of the instructions into real actions
inside the computer.
The purpose of this book is to teach you to write such instructions dedicated to
solve mathematical and engineering problems by fundamental numerical methods.
There are numerous computer languages for different purposes. Within the en
gineering area, the most widely used computer languages are Python, MATLAB,
Octave, Fortran, C, C++, and to some extent Maple, and Mathematica. How you
write the instructions (i.e. the syntax) differs between the languages. Let us use an
analogy.
Assume you are an international kind of person, having friends abroad in Eng
land, Russia and China. They want to try your favorite cake. What can you do?
Well, you may write down the recipe in those three languages and send them over.
Now, if you have been able to think correctly when writing down the recipe, and
you have written the explanations according to the rules in each language, each of
your friends will produce the same cake. Your recipe is the “computer program”,
while English, Russian and Chinese represent the “computer languages” with their
own rules of how to write things. The end product, though, is still the same cake.
Note that you may unintentionally introduce errors in your “recipe”. Depending on
the error, this may cause “baking execution” to stop, or perhaps produce the wrong
cake. In your computer program, the errors you introduce are called bugs (yes,
small insects! ... for historical reasons), and the process of fixing them is called
debugging. When you try to run your program that contains errors, you usually
get warnings or error messages. However, the response you get depends on the er
ror and the programming language. You may even get no response, but simply the
wrong “cake”. Note that the rules of a programming language have to be followed
very strictly. This differs from languages like English etc., where the meaning might
be understood even with spelling errors and “slang” included.
This book comes in two versions, one that is based on Python, and one based on
Matlab. Both Python and Matlab represent excellent programming environments
for scientific and engineering tasks. The version you are reading now, is the Python
version.
Some of Python’s strong properties deserve mention here: Many global func
tions can be placed in only one file, functions are straightforwardly transferred as
1.2 A Python Program with Variables 3
arguments to other functions, there is good support for interfacing C, C++ and For
tran code (i.e., a Python program may use code written in other languages), and
functions explicitly written for scalar input often work fine (without modification)
also with vector input. Another important thing, is that Python is available for free.
It can be downloaded from the Internet and will run on most platforms.
Readers who want to expand their scientific programming skills beyond the
introductory level of the present exposition, are encouraged to study the book
A Primer on Scientific Programming with Python [13]. This comprehensive book
is as suitable for beginners as for professional programmers, and teaches the art
of programming through a huge collection of dedicated examples. This book is
considered the primary reference, and a natural extension, of the programming
matters in the present book.
Our first example regards programming a mathematical model that predicts the po
sition of a ball thrown up in the air. From Newton’s 2nd law, and by assuming
negligible air resistance, one can derive a mathematical model that predicts the ver
tical position y of the ball at time t. From the model one gets the formula
y D v0t 0:5gt2;
where 9:81ms
which v0 is the initial upwards velocity and g is the acceleration of gravity, for
2 is a reasonable value (even if it depends on things like location
on the earth). With this formula at hand, and when v0 is known, you may plug in
a value for time and get out the corresponding height.
1 http://www.perl.com/pub/2007/12/06/soto-11.html
2http://en.wikipedia.org/wiki/Ada_Lovelace
4 1 The First Few Steps
Let us next look at a Python program for evaluating this simple formula. Assume
the program is contained as text in a file named ball.py. The text looks as follows
(file ball.py):
v0 = 5 # Initial velocity
g = 9.81 # Acceleration of gravity
t = 0.6 # Time
print y
Computer programs and parts of programs are typeset with a blue background
in this book. A slightly darker top and bottom bar, as above, indicates that the code
is a complete program that can be run as it stands. Without the bars, the code is just
a snippet and will (normally) need additional lines to run properly.
A computer program is plain text, as here in the file ball.py, which contains in
structions to the computer. Humans can read the code and understand what the
program is capable of doing, but the program itself does not trigger any actions on
a computer before another program, the Python interpreter, reads the program text
and translates this text into specific actions.
When you run your program in Python, it will interpret the text in your file line
by line, from the top, reading each line from left to right. The first line it reads is
This line is what we call a comment. That is, the line is not meant for Python to read
and execute, but rather for a human that reads the code and tries to understand what
is going on. Therefore, one rule in Python says that whenever Python encounters
1.2 A Python Program with Variables 5
the sign # it takes the rest of the line as a comment. Python then simply skips
reading the rest of the line and jumps to the next line. In the code, you see several
such comments and probably realize that they make it easier for you to understand
(or guess) what is meant with the code. In simple cases, comments are probably not
much needed, but will soon be justified as the level of complexity steps up.
The next line read by Python is
v0 = 5 # Initial velocity
are of the same kind, so having read them too, Python knows of three variables (v0,
g, t) and their values. These variables are then used by Python when it reads the
next line, the actual “formula”,
print y
This makes Python print the value of y out in that window on the screen where
you started the program. When ball.py is run, the number 1.2342 appears on the
screen.
In the code above, you see several blank lines too. These are simply skipped by
Python and you may use as many as you want to make a nice and readable layout
of the code.
Certainly, finding the answer as done by the program above could easily have been
done with a pocket calculator. No objections to that and no programming would
have been needed. However, what if you would like to have the position of the ball
6 1 The First Few Steps
for every milli-second of the flight? All that punching on the calculator would have
taken you something like four hours! If you know how to program, however, you
could modify the code above slightly, using a minute or two of writing, and easily
get all the positions computed in one go within a second. A much stronger argu
ment, however, is that mathematical models from real life are often complicated and
comprehensive. The pocket calculator cannot cope with such problems, even not
the programmable ones, because their computational power and their programming
tools are far too weak compared to what a real computer can offer.
When Python interprets some code in a file, it is concerned with every character
in the file, exactly as it was typed in. This makes it troublesome to write the code
into a file with word processors like, e.g., Microsoft Word, since such a program
will insert extra characters, invisible to us, with information on how to format the
text (e.g., the font size and type). Such extra information is necessary for the text
to be nicely formatted for the human eye. Python, however, will be much annoyed
by the extra characters in the file inserted by a word processor. Therefore, it is
fundamental that you write your program in a text editor where what you type on
the keyboard is exactly the characters that appear in the file and that Python will
later read. There are many text editors around. Some are stand-alone programs
like Emacs, Vim, Gedit, Notepad++, and TextWrangler. Others are integrated in
graphical development environments for Python, such as Spyder. This book will
primarily refer to Spyder and its text editor.
You will need access to Python and several add-on packages for doing mathemat
ical computations and display graphics. An obvious choice is to install a Python
environment for scientific computing on your machine. Alternatively, you can use
cloud services for running Python, or you can remote login on a computer system
at a school or university. Available and recommended techniques for getting access
to Python and the needed packages are documented in Appendix A.
The quickest way to get started with a Python installation for this book on your
Windows, Mac, or Linux computer, is to install Anaconda3.
Reading only does not teach you computer programming: you have to program
yourself and practice heavily before you master mathematical problem solving via
programming. Therefore, it is crucial at this stage that you write and run a Python
program. We just went through the program ball.py above, so let us next write
and run that code.
3http://continuum.io/downloads
1.3 A Python Program with a Library Function 7
But first a warning: there are many things that must come together in the right
way for ball.py to run correctly on your computer. There might be problems
with your Python installation, with your writing of the program (it is very easy
to introduce errors!), or with the location of the file, just to mention some of the
most common difficulties for beginners. Fortunately, such problems are solvable,
and if you do not understand how to fix the problem, ask somebody. Typically,
once you are beyond these common start-up problems, you can move on to learn
programming and how programs can do a lot of otherwise complicated mathematics
for you.
We describe the first steps using the Spyder graphical user interface (GUI), but
you can equally well use a standard text editor for writing the program and a termi
nal window (Terminal on Mac, Power Shell on Windows) for running the program.
Start up Spyder and type in each line of the program ball.py shown earlier. Then
run the program. More detailed descriptions of operating Spyder are found in Ap
pendix A.3.
If you have had the necessary luck to get everything right, you should now get
the number 1.2342 out in the rightmost lower window in the Spyder GUI. If so,
congratulations! You have just executed your first self-written computer program
in Python, and you are ready to go on studying this book! You may like to save the
program before moving on (File, save as).
Imagine you stand on a distance, say 10 m away, watching someone throwing a ball
upwards. A straight line from you to the ball will then make an angle with the
horizontal that increases and decreases as the ball goes up and down. Let us consider
the ball at a particular moment in time, at which it has a height of 10 m.
What is the angle of the line then? Again, this could easily be done with a cal
culator, but we continue to address gentle mathematical problems when learning
to program. Before thinking of writing a program, one should always formulate
the algorithm, i.e., the recipe for what kind of calculations that must be performed.
Here, if the ball is x m away and y m up in the air, it makes an angle  with the
ground, where tan D y=x. The angle is then tan 1.y=x/.
Let us make a Python program for doing these calculations. We introduce names
x and y for the position data x and y, and the descriptive name angle for the angle
Â. The program is stored in a file ball_angle_first_try.py:
x = 10 # Horizontal position
y = 10 # Vertical position
angle = atan(y/x)
Before
print we turn our attention
(angle/pi)*180 to the running of this program, let us take a look
at one new thing in the code. The line angle = atan(y/x), illustrates how the
function atan, corresponding to tan1 in mathematics, is called with the ratio
8 1 The First Few Steps
y/x as input parameter or argument. The atan function takes one argument,
and the computed value is returned from atan. This means that where we see
atan(y/x), a computation is performed (tan 1.y=x/) and the result “replaces” the
text atan(y/x). This is actually no more magic than if we had written just y/x:
then the computation of y/x would take place, and the result of that division would
replace the text y/x. Thereafter, the result is assigned to the name angle on the
left-hand side of =.
Note that the trigonometric functions, such as atan, work with angles in radians.
The return value of atan must hence be converted to degrees, and that is why we
perform the computation (angle/pi)*180. Two things happen in the print
statement: first, the computation of (angle/pi)*180 is performed, resulting in
a real number, and second, print prints that real number. Again, we may think that
the arithmetic expression is replaced by its results and then print starts working
with that result.
If we next execute ball_angle_first_try.py, we get an error message on
the screen saying
We have definitely run into trouble, but why? We are told that
so apparently Python does not recognize this part of the code as anything famil
iar. On a pocket calculator the inverse tangent function is straightforward to use
in a similar way as we have written in the code. In Python, however, this function
has not yet been imported into the program. A lot of functionality is available to
us in a program, but much more functionality exists in Python libraries, and to ac
tivate this functionality, we must explicitly import it. In Python, the atan function
is grouped together with many other mathematical functions in the library called
math. Such a library is referred to as a module in correct Python language. To get
access to atan in our program we have to write
Inserting this statement at the top of the program and rerunning it, leads to a new
problem: pi is not defined. The variable pi, representing , is also available in the
math module, but it has to be imported too:
It is tedious if you need quite some math functions and variables in your program,
e.g., also sin, cos, log, exp, and so on. A quick way of importing everything in
math at once, is
We will often use this import statement and then get access to all common
mathematical functions. This latter statement is inserted in a program named
ball_angle.py:
x = 10 # Horizontal position
y = 10 # Vertical position
angle = atan(y/x)
print (angle/pi)*180
We return to the problem where a ball is thrown up in the air and we have a formula
for the vertical position y of the ball. Say we are interested in y at every milli
second for the first second of the flight. This requires repeating the calculation of
y D v0t 0:5gt2 one thousand times.
We will also draw a graph of y versus t for t 2 Œ0; 1. Drawing such graphs on
a computer essentially means drawing straight lines between points on the curve,
so we need many points to make the visual impression of a smooth curve. With one
thousand points, as we aim to compute here, the curve looks indeed very smooth.
In Python, the calculations and the visualization of the curve may be done with
the program ball_plot.py, reading
v0 = 5
g = 9.81
t = linspace(0, 1, 1001)
y = v0*t - 0.5*g*t**2
plt.plot(t, y)
plt.xlabel(’t (s)’)
plt.ylabel(’y (m)’)
plt.show()
10 1 The First Few Steps
This program produces a plot of the vertical position with time, as seen in Fig
ure 1.1. As you notice, the code lines from the ball.py program in Chapter 1.2
have not changed much, but the height is now computed and plotted for a thousand
points in time!
Let us take a look at the differences between the new program and our previous
program. From the top, the first difference we notice are the lines
You see the word import here, so you understand that numpy must be a library, or
module in Python terminology. This library contains a lot of very useful functional
ity for mathematical computing, while the matplotlib.pyplot module contains
functionality for plotting curves. The above import statement constitutes a quick
way of populating your program with all necessary functionality for mathematical
computing and plotting. However, we actually make use of only a few functions
in the present program: linspace, plot, xlabel, and ylabel. Many computer
scientists will therefore argue that we should explicitly import what we need and
not everything (the star *):
Others will claim that we should do a slightly different import and prefix library
functions by the library name:
import numpy as np
import matplotlib.pyplot as plt
...
t = np.linspace(0, 1, 1001)
...
plt.plot(t, y)
plt.xlabel(’t (s)’)
plt.ylabel(’y (m)’)
We will use all three techniques, and since all of them are in so widespread use, you
should be familiar with them too. However, for the most part in this book we shall
do
from numpy import *
from matplotlib.pyplot import *
for convenience and for making Python programs that look very similar to their
Matlab counterparts.
The function linspace takes 3 parameters, and is generally called as
linspace(start, stop, n)
This is our first example of a Python function that takes multiple arguments. The
linspace function generates n equally spaced coordinates, starting with start
1.4 A Python Program with Vectorization and Plotting 11
Fig.1.1 Plot generated by the script ball_plot.py showing the vertical position of the ball at
a thousand points in time
and ending with stop. The expression linspace(0, 1, 1001) creates 1001 co
ordinates between 0 and 1 (including both 0 and 1). The mathematically inclined
reader will notice that 1001 coordinates correspond to 1000 equal-sized intervals in
Œ0; 1 and that the coordinates are then given by ti D i=1000 (i D 0; 1;:::; 1000).
The value returned from linspace (being stored in t) is an array, i.e., a collec
tion of numbers. When we start computing with this collection of numbers in the
arithmetic expression v0*t - 0.5*g*t**2, the expression is calculated for every
number in t(i.e., every ti for i D 0; 1;:::; 1000), yielding a similar collection of
1001 numbers in the resulty. That is, y is also an array.
This technique of computing all numbers “in one chunk” is referred to as vec
torization. When it can be used, it is very handy, since both the amount of code and
computation time is reduced compared to writing a corresponding for or while
loop (Chapter 2) for doing the same thing.
The plotting commands are simple:
1. plot(t, y) means plotting all the y coordinates versus all the t coordinates
2. xlabel(’t (s)’) places the text t (s) on the x axis
3. ylabel(’y (m)’) places the text y (m) on the y axis
At this stage, you are strongly encouraged to do Exercise 1.4. It builds on the
example above, but is much simpler both with respect to the mathematics and the
amount of numbers involved.
12 1 The First Few Steps
So far we have seen a few basic examples on how to apply Python programming to
solve mathematical problems. Before we can go on with other and more realistic
examples, we need to briefly treat some topics that will be frequently required in
later chapters. These topics include computer science concepts like variables, ob
jects, error messages, and warnings; more numerical concepts like rounding errors,
arithmetic operator precedence, and integer division; in addition to more Python
functionality when working with arrays, plotting, and printing.
Python can also be used interactively. That is, we do not first write a program in
a file and execute it, but we give statements and expressions to what is known as
a Python shell. We recommend to use IPython as shell (because it is superior to
alternative Python shells). With Spyder, Ipython is available at startup, appearing
as the lower right window. Following the IPython prompt In [1]: (a prompt
means a “ready sign”, i.e. the program allows you to enter a command, and different
programs often have different looking prompts), you may do calculations:
In [1]: 2+2
Out [1]: 4
In [2]: 2*3
Out [2]: 6
In [3]: 10/2
Out [3]: 5
In [4]: 2**3
Out [4]: 8
The response from IPython is preceded by Out [q]:, where q equals p when the
response is to input “number” p.
Note that, as in a program, you may have to use import before using pi or
functions like sin, cos, etc. That is, at the prompt, do the command from math
import * before you use pi or sin, etc. Importing other modules than math may
be relevant, depending on what your aim is with the computations.
You may also define variables and use formulas interactively as
In [1]: v0 = 5
In [2]: g = 9.81
In [3]: t = 0.6
In [5]: print y
------> print(y)
1.2342
1.5 More Basic Concepts 13
Sometimes you would like to repeat a command you have given earlier, or per
haps give a command that is almost the same as an earlier one. Then you can use
the up-arrow key. Pressing this one time gives you the previous command, pressing
two times gives you the command before that, and so on. With the down-arrow key
you can go forward again. When you have the relevant command at the prompt,
you may edit it before pressing enter (which lets Python read it and take action).
In [1]: x = 4
In [2]: 1.0/x+1
Out [2]: 1.25
In [3]: 1.0/(x+1)
Out [3]: 0.20000000000000001
In the first try, we see that 1.0 is divided by x (i.e., 4), giving 0.25, which is
then added to 1. Python did not understand that our complete denominator was
x+1. In our second try, we used parentheses to “group” the denominator, and we
got what we wanted. That is, almost what we wanted! Since most numbers can be
represented only approximately on the computer, this gives rise to what is called
rounding errors. We should have got 0.2 as our answer, but the inexact number
representation gave a small error. Usually, such errors are so small compared to the
other numbers of the calculation, that we do not need to bother with them. Still,
keep it in mind, since you will encounter this issue from time to time. More details
regarding number representations on a computer is given in Section 3.4.3.
Variables in Python will be of a certain type. If you have an integer, say you have
written x = 2 in some Python program, then x becomes an integer variable, i.e.,
a variable of type int. Similarly, with the statement x = 2.0, x becomes a float
14 1 The First Few Steps
variable (the word float is just computer language for a real number). In any case,
Python thinks of x as an object, of type int or float. Another common type of
variable is str, i.e. a string, needed when you want to store text. When Python
interprets x = "This is a string", it stores the text (in between the quotes) in
the variable x. This variable is then an object of type str. You may convert between
variable
will maketypes
y a floating
if it makes
point
sense.
representation
If, e.g., x isofan
x. int
Similarly,
object, writing
you may y write
= float(x)
int(x)
to produce an int if x is originally of type float. Type conversion may also occur
automatically, as shown just below.
Names of variables should be chosen so that they are descriptive. When com
puting a mathematical quantity that has some standard symbol, e.g. ˛, this should
be reflected in the name by letting the word alpha be part of the name for the cor
responding variable in the program. If you, e.g., have a variable for counting the
number of sheep, then one appropriate name could be no_of_sheep. Such naming
makes it much easier for a human to understand the written code. Variable names
may also contain any digit from 0 to 9, or underscores, but can not start with a digit.
Letters may be lower or upper case, which to Python is different. Note that certain
names in Python are reserved, meaning that you can not use these as names for vari
ables. Some examples are for, while, if, else, global, return and elif. If
you accidentally use a reserved word as a variable name you get an error message.
We have seen that, e.g., x = 2 will assign the value 2 to the variable x. But how
do we write it if we want to increase x by 4? We may write an assignment like x
= x + 4, or (giving a faster computation) x += 4. Now Python interprets this as:
take whatever value that is in x, add 4, and let the result become the new value of
x. In other words, the old value of x is used on the right hand side of =, no matter
how messy the expression might be, and the result becomes the new value of x. In
a similar way, x -= 4 reduces the value of x by 4, x *= 4 multiplies x by 4, and x
/= 4 divides x by 4, updating the value of x accordingly.
What if x = 2, i.e., an object of type int, and we add 4.0, i.e., a float? Then
automatic type conversion takes place, and the new x will have the value 6.0, i.e.,
an object of type float as seen here,
In [1]: x = 2
In [2]: x = x + 4.0
In [3]: x
Out [3]: 6.0
Note that Python programmers, and Python (in printouts), often write, e.g., 2. which
by definition is the integer 2 represented as a float.
In [1]: 1/4
Out [1]: 0
In [2]: 1.0/4
Out [2]: 0.25
We see two alternative ways of writing it, but only the last way of writing it gave
the correct (i.e., expected) result! Why?
With Python version 2, the first alternative gives what is called integer division,
i.e., all decimals in the answer are disregarded, so the result is rounded down to
the nearest integer. To avoid it, we may introduce an explicit decimal point in
either the numerator, the denominator, or in both. If you are new to programming,
this is certainly strange behavior. However, you will find the same feature in many
programming languages, not only Python, but actually all languages with significant
inheritance from C. If your numerator or denominator is a variable, say you have
1/x, you may write 1/float(x) to be on safe grounds.
Python version 3 implements mathematical real number division by / and re
quires the operator // for integer division (// is also available in Python version 2).
Although Python version 3 eliminates the problems with unintended integer divi
sion, it is important to know about integer division when doing computing in most
other languages.
Results from scientific computations are often to be reported as a mixture of text and
numbers. Usually, we want to control how numbers are formatted. For example, we
may want to write 1/3 as 0.33or 3.3333e-01(3:3333 101). Theprintcommand
is the key tool to write out text and numbers with full control of the formatting. The
first argument to print is a string with a particular syntax to specify the formatting,
the so-called printf syntax. (The peculiar name stems from the printf function in
the programming language C where the syntax was first introduced.)
Suppose we have a real number 12.89643, an integer 42, and a text ’some
message’ that we want to write out in the following two alternative ways:
The real number is first written in decimal notation with three decimals, as 12.896,
but afterwards in scientific notation as 1.290e+01. The integer is first written as
compactly as possible, while on the second line, 42 is formatted in a text field of
width equal to five characters.
The following program, formatted_print.py, applies the printf syntax to con
trol the formatting displayed above:
16 1 The First Few Steps
real = 12.89643
integer = 42
string = ’some message’
print ’real=%.3f, integer=%d, string=%s’ % (real, integer, string)
print ’real=%9.3e, integer=%5d, string=%s’ % (real, integer, string)
The output of print is a string, specified in terms of text and a set of variables
to be inserted in the text. Variables are inserted in the text at places indicated by %.
After % comes a specification of the formatting, e.g, %f (real number), %d (integer),
or %s (string). The format %9.3f means a real number in decimal notation, with 3
decimals, written in a field of width equal to 9 characters. The variant %.3f means
that the number is written as compactly as possible, in decimal notation, with three
decimals. Switching f with e or E results in the scientific notation, here 1.290e+01
or 1.290E+01. Writing %5d means that an integer is to be written in a field of width
equal to 5 characters. Real numbers can also be specified with %g, which is used
to automatically choose between decimal or scientific notation, from what gives the
most compact output (typically, scientific notation is appropriate for very small and
very large numbers and decimal notation for the intermediate range).
A typical example of when printf formatting is required, arises when nicely
aligned columns of numbers are to be printed. Suppose we want to print a column
of t values together with associated function values g.t/ D t sin.t/ in a second
column. The simplest approach would be
t0 = 2
dt = 0.55
# Unformatted print
t
print
= t0t,
+ g
0*dt; g = t*sin(t)
t = t0 + 1*dt; g = t*sin(t)
print t, g
t
print
= t0t,
+ g
2*dt; g = t*sin(t)
with output
2.0 1.81859485365
2.55 1.42209347935
3.1 0.128900053543
(Repeating the same set of statements multiple times, as done above, is not good
programming practice - one should use a for loop, as explained later in Section 2.3.)
Observe that the numbers in the columns are not nicely aligned. Using the printf
syntax ’%6.2f %8.3f’ % (t, g) for t and g, we can control the width of each
column and also the number of decimals, such that the numbers in a column are
1.5 More Basic Concepts 17
aligned under each other and written with the same precision. The output then
becomes
Formatting via printf syntax
2.00 1.819
2.55 1.422
3.10 0.129
We shall frequently use the printf syntax throughout the book so there will be
plenty of further examples.
The slots where variables are inserted are now recognized by curly braces, and
in format we list the variable names inside curly braces and their equivalent
variables in the program.
Since the printf syntax is so widely used in many programming languages,
we stick to that in the present book, but Python programmers will frequently also
meet the newer format string syntax, so it is important to be aware its existence.
1.5.6 Arrays
In the program ball_plot.py from Chapter 1.4 we saw how 1001 height com
putations were executed and stored in the variable y, and then displayed in a plot
showing y versus t, i.e., height versus time. The collection of numbers in y (or
t, respectively) was stored in what is called an array, a construction also found in
most other programming languages. Such arrays are created and treated according
to certain rules, and as a programmer, you may direct Python to compute and handle
arrays as a whole, or as individual array elements. Let us briefly look at a smaller
such collection of numbers.
Assume that the heights of four family members have been collected. These
heights may be generated and stored in an array, e.g., named h, by writing
h = zeros(4)
h[0] = 1.60
h[1] = 1.85
h[2] = 1.75
h[3] = 1.80
where the array elements appear as h[0], h[1], etc. Generally, when we read or
talk about the array elements of some array a, we refer to them by reading or saying
18 1 The First Few Steps
“a of zero” (i.e., a[0]), “a of one” (i.e., a[1]), and so on. The very first line in the
example above, i.e.
h = zeros(4)
instructs Python to reserve, or allocate, space in memory for an array h with four
elements and initial values set to 0. The next four lines overwrite the zeros with the
desired numbers (measured heights), one number for each element. Elements are,
by rule, indexed (numbers within brackets) from 0 to the last element, in this case 3.
We say that Python has zero based indexing. This differs from one based indexing
(e.g., found in Matlab) where the array index starts with 1.
As illustrated in the code, you may refer to the array as a whole by the name h,
but also to each individual element by use of the index. The array elements may
enter in computations as individual variables, e.g., writing z = h[0] + h[1] +
h[2] + h[3] will compute the sum of all the elements in h, while the result is
assigned to the variable z. Note that this way of creating an array is a bit different
from the one with linspace, where the filling in of numbers occurred automati
cally “behind the scene”.
By the use of a colon, you may pick out a slice of an array. For example,
to create a new array from the two elements h[1] and h[2], we could write
slice_h = h[1:3]. Note that the index specification 1:3 means indices 1 and 2,
i.e., the last index is not included. For the generated slice_h array, indices are as
usual, i.e., 0 and 1 in this case. The very last entry in an array may be addressed as,
e.g., h[-1].
Copying arrays requires some care since simply writing new_h = h will, when
you afterwards change elements of new_h, also change the corresponding elements
in h! That is, h[1] is also changed when writing
new_h = h
new_h[1] = 5.0
print h[1]
In this case we do not get 1.85 out on the screen, but 5.0. To really get a copy that
is decoupled from the original array, you may write new_h = copy(h). However,
copying a slice works straightforwardly (as shown above), i.e. an explicit use of
copy is not required.
1.5.7 Plotting
Sometimes you would like to have two or more curves or graphs in the same plot.
Assume we have h as above, and also an array H with the heights 0.50 m, 0.70 m,
1.90m, and 1.75 m from a family next door. This may be done with the program
plot_heights.pygiven as
1.5 More Basic Concepts 19
Figure IEX,
2.0
1.8
1.6
1.1.24.
1.0.
0.8
/
_-
0.5 _-
|-
^ool--|--|3|al
Fig.1.2 Generated plot for the heights of family members from two families
h = zeros (4)
h[0] = 1.60; h[1] = 1.85; h[2] = 1.75; h[3] = 1.80
H = zeros (4)
HIO] = 0.50; HI1] = 0.70; HI2] = 1.90; HI3] = 1.75
Then you could (in principle) do a lot of other things in your code, before you plot
the second curve by
plt.plot(family_member_no, H)
plt.hold(’off’)
Notice the use of hold here. hold(’on’) tells Python to plot also the following
curve(s) in the same window. Python does so until it reads hold(’off’). If you
do not use the hold(’on’) or hold(’off’) command, the second plot command
will overwrite the first one, i.e., you get only the second curve.
In case you would like the two curves plotted in two separate plots, you can do
this by plotting the first curve straightforwardly with
plt.plot(family_member_no, h)
plt.plot(family_member_no, H)
plt.figure()
Note how the graphs are made continuous by Python, drawing straight lines be
tween the four data points of each family. This is the standard way of doing it and
was also done when plotting our 1001 height computations with ball_plot.py in
Chapter 1.4. However, since there were so many data points then, the curve looked
nice and smooth. If preferred, one may also plot only the data points. For example,
writing
plt.plot(h, ’*’)
will mark only the data points with the star symbol. Other symbols like circles etc.
may be used as well.
There are many possibilities in Python for adding information to a plot or for
changing its appearance. For example, you may add a legend by the instruction
The command
will define the plotting range for the x axis to stretch from xmin to xmax and,
similarly, the plotting range for the y axis from ymin to ymax. Saving the figure to
file is achieved by the command
1.5 More Basic Concepts 21
For the reader who is into linear algebra, it may be useful to know that standard
matrix/vector operations are straightforward with arrays, e.g., matrix-vector multi
plication. What is needed though, is to create the right variable types (after having
imported an appropriate module). For example, assume you would like to calculate
the vectory (note that boldface is used for vectors and matrices) as y D Ax, where
A is a 2 2 matrix and x is a vector. We may do this as illustrated by the program
matrix_vector_product.pyreading
x = zeros(2)
x = mat(x)
x = transpose(x)
x[0] = 3; x[1] = 2 # Pick some values
A = zeros((2,2))
A = mat(A)
A[0,0] = 1; A[0,1] = 0
A[1,0] = 0; A[1,1] = 1
Here, x is first created as an array, just as we did above. Then the variable type
of x is changed to mat, i.e., matrix, by the line x = mat(x). This is followed
by a transpose of x from dimension 1 2 (the default dimension) to 2 1 with
the statement x = transpose(x), before some test values are plugged in. The
matrix A is first created as a two dimensional array with A = zeros((2,2))before
conversion and filling in values take place. Finally, the multiplication is performed
as y = A*x. Note the number of parentheses when creating the two dimensional
array A. Running the program gives the following output on the screen:
[[3.]
[2.]]
All programmers experience error messages, and usually to a large extent during the
early learning process. Sometimes error messages are understandable, sometimes
they are not. Anyway, it is important to get used to them. One idea is to start with
a program that initially is working, and then deliberately introduce errors in it, one
by one. (But remember to take a copy of the original working code!) For each error,
22 1 The First Few Steps
you try to run the program to see what Python’s response is. Then you know what
the problem is and understand what the error message is about. This will greatly
help you when you get a similar error message or warning later.
Very often, you will experience that there are errors in the program you have
written. This is normal, but frustrating in the beginning. You then have to find the
problem, try to fix it, and then run the program again. Typically, you fix one error
just to experience that another error is waiting around the corner. However, after
some time you start to avoid the most common beginner’s errors, and things run
more smoothly. The process of finding and fixing errors, called debugging, is very
important to learn. There are different ways of doing it too.
A special program (debugger) may be used to help you check (and do) different
things in the program you need to fix. A simpler procedure, that often brings you
a long way, is to print information to the screen from different places in the pro
gram. First of all, this is something you should do (several times) during program
development anyway, so that things get checked as you go along. However, if the
final program still ends up with error messages, you may save a copy of it, and do
some testing on the copy. Useful testing may then be to remove, e.g., the latter half
of the program (by inserting comment signs #), and insert print commands at clever
places to see what is the case. When the first half looks ok, insert parts of what
was removed and repeat the process with the new code. Using simple numbers and
doing this in parallel with hand calculations on a piece of paper (for comparison) is
often a very good idea.
Python also offers means to detect and handle errors by the program itself! The
programmer must then foresee (when writing the code) that there is a potential for
error at some particular point. If, for example, some user of the program is asked
(by the running program) to provide a number, and intends to give the number 5, but
instead writes the word five, the program could run into trouble. A try-exception
construction may be used by the programmer to check for such errors and act appro
priately (see Chapter 6.2 for an example), avoiding a program crash. This procedure
of trying an action and then recovering from trouble, if necessary, is referred to as
exception handling and is the modern way of dealing with errors in a program.
When a program finally runs without error messages, it might be tempting to
think that Ah..., I am finished!. But no! Then comes program testing, you need to
verify that the program does the computations as planned. This is almost an art and
may take more time than to develop the program, but the program is useless unless
you have much evidence showing that the computations are correct. Also, having
a set of (automatic) tests saves huge amounts of time when you further develop the
program.
Computer programs need a set of input data and the purpose is to use these data to
compute output data, i.e., results. In the previous program we have specified input
data in terms of variables. However, one often wants to get the input through some
dialog with the user. Here is one example where the program asks a question, and
the user provides an answer by typing on the keyboard:
So, after having interpreted and run the first line, Python has established the variable
age and assigned your input to it. The second line combines the calculation of
twice the age with a message printed on the screen. Try these two lines in a little
test program to see for yourself how it works.
The input function is useful for numbers, lists (Chapter 2), and tuples (Chap
ter 2). For pure text, the user must either enclose the input in quotes, or the program
must use the raw_input function instead:
There are other ways of providing input to a program as well, e.g., via a graphical
interface (as many readers will be used to) or at the command line (i.e., as param
eters succeeding, on the same line, the command that starts the program). Reading
data from a file is yet another way. Logically, what the program produces when run,
e.g. a plot or printout to the screen or a file, is referred to as program output.
Even though the main focus in this book is programming of numerical methods,
there are occasions where symbolic (also called exact or analytical) operations are
useful. Doing symbolic computations means, as the name suggests, that we do com
putations with the symbols themselves rather than with the numerical values they
could represent. Let us illustrate the difference between symbolic and numerical
computations with a little example. A numerical computation could be
x = 2
y = 3
z = x*y
print z
which will make the number 6 appear on the screen. A symbolic counterpart of this
code could be
24 1 The First Few Steps
which causes the symbolic result x*y to appear on the screen. Note that no numer
ical value was assigned to any of the variables in the symbolic computation. Only
the symbols were used, as when you do symbolic mathematics by hand on a piece
of paper.
Symbolic computations in Python make use of the SymPy package. Each symbol
is represented by a standard variable, but the name of the symbol must be declared
with Symbol(name) for a single symbol, or symbols(name1 name2 ...) for
multiple symbols.. The following script example_symbolic.pyis a quick demon
stration of some of the basic symbolic operations that are supported in Python.
x = Symbol(’y’)
y Symbol(’x’)
Other symbolic calculations like Taylor series expansion, linear algebra (with
matrix and vector operations), and (some) differential equation solving are also
possible.
Symbolic computations are also readily accessible through the (partly) free on
line tool WolframAlpha4, which applies the very advanced Mathematica5 package
as symbolic engine. The disadvantage with WolframAlpha compared to the SymPy
package is that the results cannot automatically be imported into your code and
used for further analysis. On the other hand, WolframAlpha has the advantage that
it displays many additional mathematical results related to the given problem. For
example, if we type 2x + 3x - y in WolframAlpha, it not only simplifies the ex
pression
solves thetoequation
5x - y,5xbut ityalso
D 0,makes
and calculates
plots of the
thefunction
integralf.x;y/RR
.5x C 5x y,
D y/dxdy.
The commercial Pro version can also show a step-by-step of the analytical compu
tations in the problem. You are strongly encouraged to try out these commands in
WolframAlpha:
diff(x^2, x) or diff(x**2, x)
integrate(cos(x), x)
simplify((x**2 + x**3)/x**2)
4http://www.wolframalpha.com
5http://en.wikipedia.org/wiki/Mathematica
1.5 More Basic Concepts 25
limit(sin(x)/x, x, 0)
solve(5*x - 15, x)
6http://sagemath.org/
26 1 The First Few Steps
comments on constructions that are fast or slow, but the main focus of this book
is to teach how to write correct programs, not the fastest possible programs.
The present introductory book just provides a tiny bit of all the functionality
that Python has to offer. An important source of information is the official Python
documentation website (http://docs.python.org/), which provides a Python tutorial,
the Python Library Reference, a Language Reference, and more. Several excel
lent books are also available (http://wiki.python.org/moin/PythonBooks), but not so
many with a scientific computing focus. One exception is Langtangen’s compre
hensive book A Primer on Scientific Programming with Python, Springer, 2016.
1.6 Exercises
a) Insert the word hello on the empty line above the assignment to v0.
b) Remove the # sign in front of the comment initial velocity.
c) Remove the = sign in the assignment to v0.
d) Change the reserved word print into pint.
e) Change the calculation of y to y = v0*t.
f) Change the line print y to print x.
g) Replace the statement
y = v0*t - 0.5*g*t**2
by
y = v0*t - (1/2)*g*t**2
Filename: testing_ball.py.
1.6 Exercises 27
a) Use the linspace function to compute three values of L, equally spaced on the
interval Œ1; 3.
b) Carry out by hand the computation V D L3 if L is an array with three elements.
That is, compute V for each value of L.
c) In a program, write out the result V of V = L**3 when L is an array with three
elements as computed by linspace in a). Compare the resulting volumes with
your hand calculations.
d) Make a plot of V versus L.
Filename: volume3cubes.py.
a) Compute the volume in Exercise 1.2 by using Python interactively, i.e., do the
computations at the command prompt (in a Python shell as we also say). Com
pare with what you got previously from the written program.
b) Do the same also for Exercise 1.3.
28 1 The First Few Steps
In [2]: x/y
Out[2]: 0
1. Initialize a variable x to 2.
2. Add 3 to x. Print out the result.
3. Print out the result of x + 1*2 and (x+1)*2. (Observe how parentheses make
a difference).
4. What variable type is x?
Filename: formatted_print.py.
Hint Python has a module random that contains a function by the name uniform.
Filename: drawing_random_numbers.py.
Open Access This chapter is distributed under the terms of the Creative Commons Attribution
NonCommercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/),
which permits any noncommercial use, duplication, adaptation, distribution and reproduction
in any medium or format, as long as you give appropriate credit to the original author(s) and the
source, a link is provided to the Creative Commons license and any changes made are indicated.
The images or other third party material in this chapter are included in the work’s Creative
Commons license, unless indicated otherwise in the credit line; if such material is not included
in the work’s Creative Commons license and the respective action is not permitted by statutory
regulation, users will need to obtain permission from the license holder to duplicate, adapt or
reproduce the material.
Basic Constructions
2
Very often in life, and in computer programs, the next action depends on the out
come of a question starting with “if”. This gives the possibility to branch into
different types of action depending on some criterion. Let us as usual focus on
a specific example, which is the core of so-called random walk algorithms used in
a wide range of branches in science and engineering, including materials manufac
turing and brain research. The action is to move randomly to the north (N), east (E),
south (S), or west (W) with the same probability. How can we implement such an
action in life and in a computer program?
We need to randomly draw one out of four numbers to select the direction in
which to move. A deck of cards can be used in practice for this purpose. Let the
four suits correspond to the four directions: clubs to N, diamonds to E, hearts to S,
and spades to W, for instance. We draw a card, perform the corresponding move,
and repeat the process a large number of times. The resulting path is a typical
realization of the path of a diffusing molecule.
In a computer program, we need to draw a random number, and depending on
the number, update the coordinates of the point to be moved. There are many ways
to draw random numbers and translate them into (e.g.) four random directions, but
the technical details usually depend on the programming language. Our technique
here is universal: we draw a random number in the interval Œ0; 1/ and let Œ0; 0:25/
correspond to N, Œ0:25;0:5/ to E, Œ0:5;0:75/ to S, and Œ0:75; 1/ to W. Let x and y
© The
S. Linge,
Author(s)
H.P. Langtangen,
2016 Programming for Computations – Python, 29
hold the coordinates of a point and let d be the length of the move. A pseudo code
(i.e., not “real” code, just a “sketch of the logic”) then goes like
r
if=0random 0.25 in [0,1)
<= r <number
move north: y = y + d
else if 0.25 <= r < 0.5
move east: x = x + d
else if 0.5 <= r < 0.75
move south: y = y - d
else if 0.75 <= r < 1
move west: x = x - d
Note the need for first asking about the value of r and then performing an action.
If the answer to the “if” question is positive (true), we are done and can skip the
next else if questions. If the answer is negative (false), we proceed with the next
question. The last test if 0:75 Ä r<1 could also read just else, since we here
cover all the remaining possible r values.
The exact code in Python reads
import random
r
if=0random.random()
<= r < 0.25: # random number in [0,1)
# move north
y = y + d
elif 0.25 <= r < 0.5:
# move east
x = x + d
elif 0.5 <= r < 0.75:
# move south
y = y - d
else:
# move west
x = x - d
We use else in the last test to cover the different types of syntax that is allowed.
Python recognizes the reserved words if, elif (short for else if), and else and
expects the code to be compatible with the rules of if tests:
The blocks after if, elif, or else may contain new if tests, if desired. Regard
ing colon and indent, you will see below that these are required in several other
programming constructions as well.
Working with if tests requires mastering boolean expressions. Here are some
basic boolean expressions involving the logical operators ==, !=, <, <=, >, and
2.2 Functions 31
>=. Given the assignment to temp, you should go through each boolean expression
below and determine if it is true or false.
2.2 Functions
Functions are widely used in programming and is a concept that needs to be mas
tered. In the simplest case, a function in a program is much like a mathematical
function: some input number x is transformed to some output number. One ex
ample is the tanh1.x/ function, called atan in computer code: it takes one real
number as input and returns another number. Functions in Python are more gen
eral and can take a series of variables as input and return one or more variables, or
simply nothing. The purpose of functions is two-fold:
1. to group statements into separate units of code lines that naturally belong to
gether (a strategy which may dramatically ease the problem solving process),
and/or
2. to parameterize a set of statements such that they can be written only once and
easily be re-executed with variations.
Examples will be given to illustrate how functions can be written in various con
texts.
If we modify the program ball.py from Sect. 1.2 slightly, and include a func
tion, we could let this be a new program ball_function.pyas
def y(t):
v0 = 5 # Initial velocity
g = 9.81 # Acceleration of gravity
return v0*t - 0.5*g*t**2
When Python reads and interprets this program from the top, it takes the code
from the line with def, to the line with return, to be the definition of a function
with the name y (note colon and indentation). The return statement of the function
y, i.e.
will be understood by Python as first compute the expression, then send the result
back (i.e., return) to where the function was called from. Both def and return are
reserved words. The function depends on t, i.e., one variable (or we say that it takes
one argument or input parameter), the value of which must be provided when the
function is called.
What actually happens when Python meets this code? The def line just tells
Python that here is a function with name y and it has one argument t. Python does
not look into the function at this stage (except that it checks the code for syntax
errors). When Python later on meets the statement print y(time), it recognizes
a function call y(time) and recalls that there is a function y defined with one ar
gument. The value of time is then transferred to the y(t) function such that t
= time becomes the first action in the y function. Then Python executes one line
at a time in the y function. In the final line, the arithmetic expression v0*t -
0.5*g*t**2 is computed, resulting in a number, and this number (or more pre
cisely, the Python object representing the number) replaces the call y(time) in the
calling code such that the word print now precedes a number rather than a function
call.
Python proceeds with the next line and sets time to a new value. The next print
statement triggers a new call to y(t), this time t is set to 0.9, the computations
are done line by line in the y function, and the returned result replaces y(time).
Instead of writing print y(time), we could alternatively have stored the returned
result from the y function in a variable,
h = y(time)
print h
def check_sign(x):
if x > 0:
return ’x is positive’
elif x < 0:
return ’x is negative’
else:
return ’x is zero’
Remember that only one of the branches is executed for a single call on check_
sign, so depending on the number x, the return may take place from any of the
three return alternatives.
one single return statement. Be prepared for critical comments if you return
wherever you want...
An expression you will often encounter when dealing with programming, is main
program, or that some code is in main. This is nothing particular to Python, and sim
ply refers to that part of the program which is outside functions. However, note that
the def line of functions is counted into main. So, in ball_function.py above,
all statements outside the function y are in main, and also the line def y(t):.
A function may take no arguments, or many, in which case they are just listed
within the parentheses (following the function name) and separated by a comma.
Let us illustrate. Take a slight variation of the ball example and assume that the
ball is not thrown straight up, but at an angle, so that two coordinates are needed to
specify its position at any time. According to Newton’s laws (when air resistance is
negligible), the vertical position is given by y.t/ D v0yt0:5gt2 and the horizontal
position by x.t/ D v0xt. We can include both these expressions in a new version of
our program that prints the position of the ball for chosen times. Assume we want
to evaluate these expressions at two points in time, t D 0:6s and t D 0:9s. We
can pick some numbers for the initial velocity components v0y and v0x, name the
program ball_position_xy.py, and write it for example as
initial_velocity_x = 2.0
initial_velocity_y = 5.0
Now we compute and print the two components for the position, for each of the
two chosen points in time. Notice how each of the two functions now takes two
arguments. Running the program gives the output
1.2 1.2342
1.8 0.52695
A function may also have no return value, in which case we simply drop the re
turn statement, or it may return more than one value. For example, the two functions
we just defined could alternatively have been written as one:
Notice the two return values which are simply separated by a comma. When call
ing the function (and printing), arguments must appear in the same order as in the
function definition. We would then write
The two returned values from the function could alternatively have been assigned
to variables, e.g., as
The variables x_pos and y_pos could then have been printed or used in other ways
in the code.
There are possibilities for having a variable number of function input and output
parameters (using *args and **kwargs constructions for the arguments). How
ever, we do not go further into that topic here.
Variables that are defined inside a function, e.g., g in the last xy function, are
local variables. This means they are only known inside the function. Therefore,
if you had accidentally used g in some calculation outside the function, you would
have got an error message. The variable time is defined outside the function and is
therefore a global variable. It is known both outside and inside the function(s). If
you define one global and one local variable, both with the same name, the function
only sees the local one, so the global variable is not affected by what happens with
the local variable of the same name.
The arguments named in the heading of a function definition are by rule local
variables inside the function. If you want to change the value of a global variable
inside a function, you need to declare the variable as global inside the function.
That is, if the global variable was x, we would need to write global x inside the
function definition before we let the function change it. After function execution,
x would then have a changed value. One should strive to define variables mostly
where they are needed and not everywhere.
Another very useful way of handling function parameters in Python, is by defin
ing parameters as keyword arguments. This gives default values to parameters and
allows more freedom in function calls, since the order and number of parameters
may vary.
Let us illustrate the use of keyword arguments with the function xy. Assume we
defined xy as
Here, t is an ordinary or positional argument, whereas v0x and v0y are keyword
arguments or named arguments. Generally, there can be many positional argu
ments and many keyword arguments, but the positional arguments must always be
listed before the keyword arguments in function definition. Keyword arguments
are given default values, as shown here with v0x and v0y, both having zero as de
2.2 Functions 35
fault value. In a script, the function xy may now be called in many different ways.
For example,
print xy(0.6)
would make xy perform the computations with t = 0.6 and the default values (i.e
zero) of v0x and v0y. The two numbers returned from xy are printed to the screen.
If we wanted to use another initial value for v0y, we could, e.g., write
print xy(0.6,v0y=4.0)
which would make xy perform the calculations with t = 0.6, v0x = 0 (i.e. the
default value) and v0y = 4.0. When there are several positional arguments, they
have to appear in the same order as defined in the function definition, unless we
explicitly use the names of these also in the function call. With explicit name spec
ification in the call, any order of parameters is acceptable. To illustrate, we could,
e.g., call xy as
Note that other functions may be called from within other functions, and function
input parameters are not required to be numbers. Any object will do, e.g., string
variables or other functions.
Functions are straightforwardly passed as arguments to other functions, as illus
trated by the following script function_as_argument.py:
x = 2; y = 3
print treat_xy(sum_xy, x, y)
print treat_xy(prod_xy, x, y)
36 2 Basic Constructions
When run, this program first prints the sum of x and y (i.e., 5), and then it
prints the product (i.e., 6). We see that treat_xy takes a function name as its first
parameter. Inside treat_xy, that function is used to actually call the function that
was given as input parameter. Therefore, as shown, we may call treat_xy with
either sum_xy or prod_xy, depending on whether we want the sum or product of x
and y to be calculated.
Functions may also be defined within other functions. It that case, they become
local functions, or nested functions, known only to the function inside which they
are defined. Functions defined in main are referred to as global functions. A nested
function has full access to all variables in theparent function, i.e. the function within
which it is defined.
Short functions can be defined in a compact way, using what is known as
a lambda function:
f = lambda x, y: x + 2*y
# Equivalent
def f(x, y):
return x + 2*y
The syntax consists of lambda followed by a series of arguments, colon, and some
Python expression resulting in an object to be returned from the function. Lambda
functions are particularly convenient as function arguments:
In [2]: a = np.zeros(1000000)
We notice that there is some overhead in function calls. The impact of the over
head reduces quickly with the amount of computational work inside the function.
Many computations are repetitive by nature and programming languages have cer
tain loop structures to deal with this. Here we will present what is referred to as
a for loop (another kind of loop is a while loop, to be presented afterwards). Assume
you want to calculate the square of each integer from 3 to 7. This could be done
with the following two-line program.
for i
print
in [3,
i**2
4, 5, 6, 7]:
This call makes range return the integers from (and including) start, up to
(but excluding!) stop, in steps of step. Note here that stop-1 is the last integer
included. With range, the previous example would rather read
By default, if range is called with only two parameters, these are taken to be
start and stop, in which case a step of 1 is understood. If only a single parameter
is used in the call to range, this parameter is taken to be stop. The default step of
38 2 Basic Constructions
1 is then used (combined with the starting at 0). Thus, calling range, for example,
as
range(6)
v0 = 5 # Initial velocity
g = 9.81 # Acceleration of gravity
t = linspace(0, 1, 1000) # 1000 points in time interval
y = v0*t - 0.5*g*t**2 # Generate all heights
There is nothing new here, except the for loop construction, so let us look at it
in more detail. As explained above, Python understands that a for loop is desired
when it sees the word for. The range() function will produce integers from, and
including, 1, up to, and including, 999, i.e. 1000 1. The value in y[0] is used
as the preliminary largest height, so that, e.g., the very first check that is made is
testing whether y[1] is larger than this height. If so, y[1] is stored as the largest
height. The for loop then updates i to 2, and continues to check y[2], and so on.
Each time we find a larger number, we store it. When finished, largest_height
2.3 For Loops 39
will contain the largest number from the array y. When you run the program, you
get
largest_number = A[0][0]
for for
i inj
ifrange(4):
in
A[i][j]
largest_number
range(4):
> largest_number:
= A[i][j]
Here, all the j indices (0 - 3) will be covered for each value of index i. First, i
stays fixed at i = 0, while j runs over all its indices. Then, i stays fixed at i = 1
while j runs over all its indices again, and so on. Sketch A on a piece of paper and
follow the first few loop iterations by hand, then you will realize how the double
loop construction works. Using two loops is just a special case of using multiple or
nested loops, and utilizing more than two loops is just a straightforward extension
of what was shown here. Note, however, that the loop index name in multiple loops
must be unique to each of the nested loops. Note also that each nested loop may
have as many code lines as desired, both before and after the next inner loop.
The vectorized computation of heights that we did in ball_plot.py (Sect. 1.4)
could alternatively have been done by traversing the time array (t) and, for each t
it is important
element, computing
to know
the height
that vectorization
according togoes
the formula
much quicker. gt2. However,
y D v0t So12when
speed is
important, vectorization is valuable.
x D NX
2 i;
iD1
i.e., summing up the N first even numbers. For some given N, say N D 5, x
would typically be computed in a computer program as:
40 2 Basic Constructions
N = 5
x = 0
for i in range(1, N+1):
x += 2*i
print x
Executing this code will print the number 30 to the screen. Note in particular
how the accumulation variable x is initialized to zero. The value of x then gets
updated with each iteration of the loop, and not until the loop is finished will
x have the correct value. This way of building up the value is very common in
programming, so make sure you understand it by simulating the code segment
above by hand. It is a technique used with loops in any programming language.
Python also has another standard loop construction, the while loop, doing iterations
with a loop index very much like the for loop. To illustrate what such a loop may
look like, we consider another modification of ball_plot.pyin Sect. 1.4. We will
now change it so that it finds the time of flight for the ball. Assume the ball is
thrown with a slightly lower initial velocity, say 4:5 ms1, while everything else is
kept unchanged. Since we still look at the first second of the flight, the heights at the
end of the flight become negative. However, this only means that the ball has fallen
below its initial starting position, i.e., the height where it left the hand, so there is
no problem with that. In our array y we will then have a series of heights which
find
towards
the time
the end
whenof yheights
become start
negative.
to get negative,
Let us, in i.e.,
a program
when the
named
ball ball_time.py
crosses y D 0.
plt.show()
2.5 Lists and Tuples – Alternatives to Arrays 41
y=0 at 0.917417417417
The new thing here is the while loop only. The loop (note colon and indentation)
will run as long as the boolean expression y[i] > 0 evaluates to True. Note that
the programmer introduced a variable (the loop index) by the name i, initialized it
(i = 0) before the loop, and updated it (i += 1) in the loop. So for each iteration,
i is explicitly increased by 1, allowing a check of successive elements in the array y.
Compared to a for loop, the programmer does not have to specify the number
of iterations when coding a while loop. It simply runs until the boolean expression
becomes False. Thus, a loop index (as we have in a for loop) is not required. Fur
thermore, if a loop index is used in a while loop, it is not increased automatically;
it must be done explicitly by the programmer. Of course, just as in for loops and
if blocks, there might be (arbitrarily) many code lines in a while loop. Any for
loop may also be implemented as a while loop, but while loops are more general
so not all of them can be expressed as a for loop.
A problem to be aware of, is what is usually referred to as an infinite loop. In
those unintentional (erroneous) cases, the boolean expression of the while test
never evaluates to False, and the program can not escape the loop. This is one
of the most frequent errors you will experience as a beginning programmer. If you
accidentally enter an infinite loop and the program just hangs forever, press Ctrl+c
to stop the program.
We have seen that a group of numbers may be stored in an array that we may treat
as a whole, or element by element. In Python, there is another way of organiz
ing data that actually is much used, at least in non-numerical contexts, and that is
a construction called list.
A list is quite similar to an array in many ways, but there are pros and cons
to consider. For example, the number of elements in a list is allowed to change,
whereas arrays have a fixed length that must be known at the time of memory allo
cation. Elements in a list can be of different type, i.e you may mix integers, floats
and strings, whereas elements in an array must be of the same type. In general, lists
provide more flexibility than do arrays. On the other hand, arrays give faster com
putations than lists, making arrays the prime choice unless the flexibility of lists is
needed. Arrays also require less memory use and there is a lot of ready-made code
for various mathematical operations. Vectorization requires arrays to be used.
The range() function that we used above in our for loop actually returns a list.
If you for example write range(5) at the prompt in ipython, you get [0, 1,
2, 3, 4] in return, i.e., a list with 5 numbers. In a for loop, the line for i in
range[5] makes i take on each of the numbers 0; 1; 2; 3; 4 in turn, as we saw
above. Writing, e.g., x = range(5), gives a list by the name x, containing those
five numbers. These numbers may now be accessed (e.g., as x[2], which contains
the number 2) and used in computations just as we saw for array elements. As with
arrays, indices run from 0 to n 1, when n is the number of elements in a list. You
may convert a list to an array by x = array(L).
42 2 Basic Constructions
x = [’hello’, 4, 3.14, 6]
giving a list where x[0] contains the string hello, x[1] contains the integer 4, etc.
We may add and/or delete elements anywhere in the list as shown in the following
example.
x = [’hello’, 4, 3.14, 6]
x.insert(0, -2) # x then becomes [-2, ’hello’, 4, 3.14, 6]
del x[3] # x then becomes [-2, ’hello’, 4, 6]
x.append(3.14) # x then becomes [-2, ’hello’, 4, 6, 3.14]
Note the ways of writing the different operations here. Using append()will always
increase the list at the end. If you like, you may create an empty list as x = []
before you enter a loop which appends element by element. If you need to know
the length of the list, you get the number of elements from len(x), which in our
case is 5, after appending 3.14above. This function is handy if you want to traverse
all list elements by index, since range(len(x)) gives you all legal indices. Note
that there are many more operations on lists possible than shown here.
Previously, we saw how a for loop may run over array elements. When we want
to do the same with a list in Python, we may do it as this little example shows,
x = [’hello’, 4, 3.14, 6]
for e in x:
print ’x element: ’, e
print ’This was all the elements in the list x’
This is how it usually is done in Python, and we see that e runs over the elements
of x directly, avoiding the need for indexing. Be aware, however, that when loops
are written like this, you can not change any element in x by “changing” e. That is,
writing e += 2 will not change anything in x, since e can only be used to read (as
opposed to overwrite) the list elements.
There is a special construct in Python that allows you to run through all elements
of a list, do the same operation on each, and store the new elements in another list.
It is referred to as list comprehension and may be demonstrated as follows.
List_1 = [1, 2, 3, 4]
List_2 = [e*10 for e in List_1]
This will produce a new list by the name List_2, containing the elements 10,
20, 30 and 40, in that order. Notice the syntax within the brackets for List_2,
for e in List_1 signals that e is to successively be each of the list elements in
List_1, and for each e, create the next element in List_2 by doing e*10. More
generally, the syntax may be written as
In some cases, it is required to run through 2 (or more) lists at the same time.
Python has a handy function called zip for this purpose. An example of how to use
zip is provided in the code file_handling.pybelow.
We should also briefly mention about tuples, which are very much like lists, the
main difference being that tuples cannot be changed. To a freshman, it may seem
strange that such “constant lists” could ever be preferable over lists. However, the
property of being constant is a good safeguard against unintentional changes. Also,
it is quicker for Python to handle data in a tuple than in a list, which contributes to
faster code. With the data from above, we may create a tuple and print the content
by writing
x = (’hello’, 4, 3.14, 6)
for e in x:
print ’x element: ’, e
print ’This was all the elements in the tuple x’
Trying insert or append for the tuple gives an error message (because it cannot
be changed), stating that the tuple object has no such attribute.
Input data for a program often come from files and the results of the computations
are often written to file. To illustrate basic file handling, we consider an example
where we read x and y coordinates from two columns in a file, apply a function f
to the y coordinates, and write the results to a new two-column data file. The first
line of the input file is a heading that we can just skip:
# x and y coordinates
1.0 3.44
2.0 4.8
3.5 6.61
4.0 5.0
The relevant Python lines for reading the numbers and writing out a similar file are
given in the file file_handling.py
filename
infile = open(filename,
= ’tmp.dat’ ’r’) # Open file for reading
# Transform y coordinates
from math import log
def f(y):
return log(y)
for i in range(len(y)):
y[i] = f(y[i])
Such a file with a comment line and numbers in tabular format is very com
mon so numpy has functionality to ease reading and writing. Here is the same
example using the loadtxt and savetxt functions in numpy for tabular data (file
file_handling_numpy.py):
filename = ’tmp.dat’
import numpy
data = numpy.loadtxt(filename, comments=’#’)
x = data[:,0]
= numpy.log(y) # insert transformed y back in array
y = data[:,1]
data[:,1]
filename = ’tmp_out.dat’
outfile ==open(filename,
filename ’tmp_out.dat’ ’w’) # open file for writing
2.7 Exercises
a) Change the first line from def y(t): to def y(t), i.e., remove the colon.
b) Remove the indent in front of the statement v0 = 5 inside the function y, i.e.,
shift the text four spaces to the left.
2.7 Exercises 45
c) Now let the statement v0 = 5 inside the function y have an indent of three
spaces (while the remaining two lines of the function have four).
d) Remove the left parenthesis in the first statement def y(t):
e) i.e.,
Changeremove
the first
the parameter
line of thet.function definition from def y(t): to def y():,
f) Change the first occurrence of the statement print y(time) to print y().
Filename: errors_colon_indent_etc.py.
a = input(’Give an integer a: ’)
b = input(’Give an integer b: ’)
if aprint
< b: "a is the smallest of the two numbers"
elifprint
a == "a
b: and b are equal"
else:
print "a is the largest of the two numbers"
Proceed by writing the program, and then run it a few times with different values
for a and b to confirm that it works as intended. In particular, choose combinations
for a and b so that all three branches of the if construction get tested.
Filename: compare_a_and_b.py.
The vertices (“corners”) of the polygon have coordinates .x1;y1/, .x2;y2/, :::,
.xn;yn/, numbered either in a clockwise or counter clockwise fashion. The area A
of the polygon can amazingly be computed by just knowing the boundary coordi
nates:
AD 1 2 j.x1y2 C x2y3 C C xn 1yn C xny1/
Write a function polyarea(x, y) that takes two coordinate arrays with the ver
tices as arguments and returns the area. Assume that x and y are either lists or
arrays.
Test the function on a triangle, a quadrilateral, and a pentagon where you can
calculate the area by alternative methods for comparison.
Hint Since Python lists and arrays has 0 as their first index, it is wise to rewrite the
mathematical formula in terms of vertex coordinates numbered as x0;x1;:::;xn 1
and y0;y1;:::;yn1.
Filename: polyarea.py.
some_number = 0
i = 1
while i < 11
some_number += 1
print some_number
a) Identify the errors in the program by just reading the code and simulating the
program by hand.
b) Write a new version of the program with errors corrected. Run this program and
confirm that it gives the correct output.
Filename: while_loop_errors.py.
Exercise
Up through
2.11:
history,
Compute
great minds have developed different computational schemes
for the number . We will here consider two such schemes, one by Leibniz (1646
1716), and one by Euler (1707 1783).
The scheme by Leibniz may be written
D8
1X
;
.4kC1/.4kC3/
kD0 1
while one form of the Euler scheme may appear as
v
u
u 1X 1
Dt6 :
k2
kD1
If only the first N terms of each sum are used as an approximation to , each
modified scheme will have computed with some error.
Write a program that takes N as input from the user, and plots the error develop
ment with both schemes as the number of iterations approaches N. Your program
should also print out the final error achieved with both schemes, i.e. when the num
ber of terms is N. Run the program with N D 100 and explain briefly what the
graphs show.
Filename: compute_pi.py.
a) In a deck of cards, each card is a combination of a rank and a suit. There are 13
ranks: ace (A), 2, 3, 4, 5, 6, 7, 8, 9, 10, jack (J), queen (Q), king (K), and four
suits: clubs (C), diamonds (D), hearts (H), and spades (S). A typical card may
be D3. Write statements that generate a deck of cards, i.e., all the combinations
CA, C2, C3, and so on to SK.
b) A vehicle registration number is on the form DE562, where the letters vary from
A to Z and the digits from 0 to 9. Write statements that compute all the possible
registration numbers and stores them in a list.
2.7 Exercises 49
c) Generate all the combinations of throwing two dice (the number of eyes can
vary from 1 to 6). Count how many combinations where the sum of the eyes
equals 7.
Filename: combine_sets.py.
Remarks For large N, this program computes the probability M=N of getting six
eyes when throwing a die.
b) Write another function with a loop where the user is asked for a time on the
interval Œ0; N and the corresponding (interpolated) y value is written to the
screen. The loop is terminated when the user gives a negative time.
c) Use the following measurements: 4:4; 2:0; 11:0; 21:5; 7:5, corresponding to
times 0; 1;:::; 4 (min), and compute interpolated values at t D 2:5 and t D 3:1
min. Perform separate hand calculations to check that the output from the
program is correct.
Filename: linear_interpolation.py.
f.xi/ f.c/
D a; i D 1; 2; :::; 100;
xi c
where a is the slope of the line and c defines a fixed point .c; f.c// on the line. Let
c D 2 here.
Filename: test_straight_line.py.
a) Make a function that computes the error between the straight line f.x/ D axCb
and the measurements:
e D 5X 2:
.axi C b yi/
iD1
b) Make a function with a loop where you give a and b, the corresponding value of
e is written to the screen, and a plot of the straight line f.x/ D ax C b together
with the discrete measurements is shown.
Hint To make the plotting from the loop to work, you may have to insert from
matplotlib.pylab import * at the top of the script and also add show() after
the plot command in the loop.
c) Given the measurements 0:5; 2:0; 1:0; 1:5; 7:5, at times 0; 1; 2; 3; 4, use the func
tion in b) to interactively search for a and b such that e is minimized.
Filename: Fitting
Remarks fit_straight_line.py.
a straight line to measured data points is a very common task. The
SN.t/ D NX
bn sin.nt/; (2.1)
nD1
where the coefficients bn must be adjusted such that SN .t/ is a good approximation
to f.t/. We shall in this exercise adjust bn by a trial-and-error process.
Hint To make this part of your program work, you may have to insert from
matplotlib.pylab import * at the top and also add show() after the plot
command in the loop.
52 2 Basic Constructions
Filename: fit_sines.py.
Remarks
gene = ’AGTCAATGGAATAGGCCAAGCGAATATTTGGGCTACCA’
We may traverse this string, letter by letter, by the for loop for letter in gene.
The length of the string is given by len(gene), so an alternative traversal over an
index i is for i in range(len(gene)). Letter number i is reached through
gene[i], and a substring from index i up to, but not including j, is created by
gene[i:j].
a) Write a function freq(letter, text) that returns the frequency of the letter
letter in the string text, i.e., the number of occurrences of letter divided
by the length of text. Call the function to determine the frequency of C and G
in the gene string above. Compute the frequency by hand too.
b) Write a function pairs(letter, text) that counts how many times a pair
of the letter letter (e.g., GG) occurs within the string text. Use the function
to determine how many times the pair AA appears in the string gene above.
Perform a manual counting too to check the answer.
2.7 Exercises 53
Filename: count_substrings.py.
Remarks You are supposed to solve the tasks using simple programming with
loops and variables. While a) and b) are quite straightforward, c) quickly involves
demanding logic. However, there are powerful tools available in Python that can
solve the tasks efficiently in very compact code: a) text.count(letter)/float
(len(text)); b)text.count(letter*2); c) len(re.findall(’G[AT]+?GG’,
text)). That is, there is rich functionality for analysis of text in Python and this is
particularly useful in analysis of gene sequences.
Open Access This chapter is distributed under the terms of the Creative Commons Attribution
NonCommercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/),
which permits any noncommercial use, duplication, adaptation, distribution and reproduction
in any medium or format, as long as you give appropriate credit to the original author(s) and the
source, a link is provided to the Creative Commons license and any changes made are indicated.
The images or other third party material in this chapter are included in the work’s Creative
Commons license, unless indicated otherwise in the credit line; if such material is not included
in the work’s Creative Commons license and the respective action is not permitted by statutory
regulation, users will need to obtain permission from the license holder to duplicate, adapt or
reproduce the material.
Computing Integrals
3
where f.x/ D dF
dx:
© The Author(s) 2016 55
S. Linge, H.P. Langtangen, Programming for Computations – Python,
Texts in Computational Science and Engineering 15, DOI 10.1007/978-3-319-32428-9_3
56 3 Computing Integrals
The major problem with this procedure is that we need to find the anti-derivative
F.x/ corresponding to a given f.x/. For some relatively simple integrands f.x/,
finding F.x/ is a doable task, but it can very quickly become challenging, even
impossible!
The method (3.1) provides an exact or analytical value of the integral. If we
relax the requirement of the integral being exact, and instead look for approximate
values, produced by numerical methods, integration becomes a very straightforward
task for any given f.x/ (!).
The downside of a numerical method is that it can only find an approximate an
swer. Leaving the exact for the approximate is a mental barrier in the beginning, but
remember that most real applications of integration will involve an f.x/ function
that contains physical parameters, which are measured with some error. That is,
f.x/ is very seldom exact, and then it does not make sense to compute the integral
with a smaller error than the one already present in f.x/.
Another advantage of numerical methods is that we can easily integrate a func
tion f.x/ that is only known as samples, i.e., discrete values at some x points,
and not as a continuous function of x expressed through a formula. This is highly
relevant when f is measured in a physical experiment.
f.x/dx: (3.2)
a
Most numerical methods for computing this integral split up the original integral
into a sum of several integrals, each covering a smaller part of the original inte
gration interval Œa;b. This re-writing of the integral is based on a selection of
integration points xi, i D 0; 1;:::; n that are distributed on the interval Œa;b.
Integration points may, or may not, be evenly distributed. An even distribution sim
plifies expressions and is often sufficient, so we will mostly restrict ourselves to that
choice. The integration points are then computed as
where
hD bna
: (3.4)
Given the integration points, the original integral is re-written as a sum of inte
grals, each integral being computed over the sub-interval between two consecutive
integration points. The integral in (3.2) is thus expressed as
bZ
a f.x/dx D Zx1 Zx2 Zxn
f.x/dx C f.x/dx C ::: C f.x/dx: (3.5)
x0 x1 xn 1
Proceeding from (3.5), the different integration methods will differ in the way
they approximate each integral on the right hand side. The fundamental idea is that
each term is an integral over a small interval Œxi;xiC1, and over this small interval,
it makes sense to approximate f by a simple shape, say a constant, a straight line,
or a parabola, which we can easily integrate by hand. The details will become clear
in the coming examples.
1Z
v.t/dt; (3.7)
0
We can therefore compute the exact value of the integral as V.1/ V.0/ 1:718
(rounded to 3 decimals for convenience).
If we replace the true graph in Fig. 3.1 by a set of straight line segments, we
may view the area rather as composed of trapezoids, the areas of which are easy to
compute. This is illustrated in Fig. 3.2, where 4 straight line segments give rise to
4 trapezoids, covering the time intervals Œ0; 0:2/, Œ0:2;0:6/, Œ0:6;0:8/ and Œ0:8; 1:0.
Note that we have taken the opportunity here to demonstrate the computations with
time intervals that differ in size.
58 3 Computing Integrals
Fig. 3.1 The integral of v.t/ interpreted as the area under the graph of v
Fig. 3.2 Computing approximately the integral of a function as the sum of the areas of the trape
zoids
3.2 The Composite Trapezoidal Rule 59
The areas of the 4 trapezoids shown in Fig. 3.2 now constitute our approximation
to the integral (3.7):
1Z Âv.0/Cv.0:2/2Ã C h2 Â Ã
v.0:2/Cv.0:6/2
v.t/dt h1
0
2 v.0:8/ Ã C h4 Â v.0:8/ C
C h3 Âv.0:6/ C 2 v.1:0/ Ã
; (3.9)
where
h1 D .0:20:0/; (3.10)
h2 D .0:6 0:2/; (3.11)
h3 D .0:8 0:6/; (3.12)
h4 D .1:0 0:8/ (3.13)
With v.t/ D 3t2et3, each term in (3.9) is readily computed and our approximate
computation gives
1Z
v.t/dt 1:895: (3.14)
0
Compared to the true answer of 1.718, this is off by about 10%. However, note
that we used just 4 trapezoids to approximate the area. With more trapezoids, the
approximation would have become better, since the straight line segments in the
upper trapezoid side then would follow the graph more closely. Doing another hand
calculation with more trapezoids is not too tempting for a lazy human, though,
but it is a perfect job for a computer! Let us therefore derive the expressions for
approximating the integral by an arbitrary number of trapezoids.
bZ
a f.x/dxD Zx1 f.x/dx C Zx2 Zxn
f.x/dxC ::: C f.x/dx;
x0 x1 xn1
f.x0/C2 f.x1/C hf.x1/ C
2 f.x2/C:::
h
C hf.xn1/ C f.xn/
2 (3.15)
60 3 Computing Integrals
3.2.2 Implementation
in our problem. Although simple in principle, the practical steps are confusing for
many because the notation in the abstract problem in (3.17) differs from the notation
in our special problem. Clearly, the f, x, and h in (3.17) correspond to v, t, and
perhaps t for the trapezoid width in our special problem.
The first alternative in the box above sounds less abstract and therefore more
attractive to many. Nevertheless, as we hope will be evident from the examples,
the second alternative is actually the simplest and most reliable from both a math
ematical and programming point of view. These authors will claim that the second
3.2 The Composite Trapezoidal Rule 61
alternative is the essence of the power of mathematics, while the first alternative is
the source of much confusion about mathematics!
mula (3.17) we want
Implementation withthe
functions
corresponding
For the
Python
integral
function
Rab f.x/dx
trapezoid
computed
to take
by the
anyfor
f,
a, b, and n as input and return the approximation to the integral.
We write a Python function trapezoidal in a file trapezoidal.py as close
as possible to the formula (3.17), making sure variable names correspond to the
mathematical notation:
Solving our specific problem in a session Just having the trapezoidal func
tion as the only content of a file trapezoidal.py automatically makes that file
a module that we can import and test in an interactive session:
Let us compute the exact expression and the error in the approximation:
we just put the statements we otherwise would have put in a main program, inside
a function:
def application():
from math import exp
v = lambda t: 3*(t**2)*exp(t**3)
n = input(’n: ’)
numerical = trapezoidal(v, 0, 1, n)
Now we compute our special problem by calling application()as the only state
ment in the main program. Both the trapezoidal and application functions
reside in the file trapezoidal.py, which can be run as
Terminal
Terminal>
n: 4 python trapezoidal.py
n=4: 1.9227167504675762, error: -0.204435
if __name__ == ’__main__’:
application()
Terminal
Terminal>
n: 400 python trapezoidal.py
n=400: 1.7183030649495579, error: -2.12365e-05
3.2 The Composite Trapezoidal Rule 63
a = 0.0; b = 1.0
n = input(’n: ’)
dt = float(b - a)/n
1. We need to reformulate (3.17) for our special problem with a different notation.
2. The integrand 3t2et 3 is inserted many times in the code, which quickly leads to
errors.
3. A lot of edits are necessary to use the code to compute a different integral –
these edits are likely to introduce errors.
1Replacing h by t is not strictly required as many use h as interval also along the time axis.
Nevertheless, t is an even more popular notation for a small time interval, so we adopt that
common notation.
64 3 Computing Integrals
F = lambda t: exp(t**3)
exact_value = F(b) - F(a)
error = abs(exact_value - numerical)
rel_error
print ’n=%d: %.16f, error: %g’ % (n, numerical, error)
= (error/exact_value)*100
Unfortunately, the two other problems remain and theyR are fundamental.
x2
weSuppose
need to change
you want
in the
to compute
previousanother
code to integral,
computesaythe new dx. How much do
1:11 eintegral?
Not so much:
These changes are straightforward to implement, but they are scattered around in
the program, a fact that requires us to be very careful so we do not introduce new
programming errors while we modify the code. It is also very easy to forget to make
a required change.
(or ine ax2program)
With
1:11 dx previous code in trapezoidal.py, we can compute the new integral
the
R
without touching the mathematical algorithm. In an interactive session
we can just do
When you now look back at the two solutions, the flat special-purpose program
and the function-based program with the general-purpose function trapezoidal,
you hopefully realize that implementing a general mathematical algorithm in a gen
eral function requires somewhat more abstract thinking, but the resulting code can
be used over and over again. Essentially, if you apply the flat special-purpose style,
you have to retest the implementation of the algorithm after every change of the
program.
2You cannot integrate ex2 by hand, but this particular integral is appearing so often in so many
contexts that the integral is a special function, called the Error function (http://en.wikipedia.org/
wiki/Error_function) and written erf.x/. In a code, you can call erf(x). The erf function is
found in the math module.
3.3 The Composite Midpoint Method 65
The present integral problems result in short code. In more challenging engineer
ing problems the code quickly grows to hundreds and thousands of lines. Without
abstractions in terms of general algorithms in general reusable functions, the com
plexity of the program grows so fast that it will be extremely difficult to make sure
that the program works properly.
Another advantage of packaging mathematical algorithms in functions is that
a function can be reused by anyone to solve a problem by just calling the function
with a proper set of arguments. Understanding the function’s inner details is not
necessary to compute a new integral. Similarly, you can find libraries of functions
on the Internet and use these functions to solve your problems without specific
knowledge of every mathematical detail in the functions.
This desirable feature has its downside, of course: the user of a function may
misuse it, and the function may contain programming errors and lead to wrong an
swers. Testing the output of downloaded functions is therefore extremely important
before relying on the results.
The idea Rather than approximating the area under a curve by trapezoids, we can
use plain rectangles (Fig. 3.3). It may sound less accurate to use horizontal lines
and not skew lines following the function to be integrated, but an integration method
based on rectangles (the midpoint method) is in fact slightly more accurate than the
one based on trapezoids!
Fig. 3.3 Computing approximately the integral of a function as the sum of the areas of the rect
angles
66 3 Computing Integrals
+ haf (w º + haf (e w)
+ + •
2 2
where h 1, he, hā, and ha are the widths of the sub-intervals, used previously with
the trapezoidal method and defined in (3.10)–(3.13).
With f(t) = 31°eſ °, the approximation becomes 1.632. Compared with the true
answer (1.718), this is about 5% too small, but it is better than what we got with
the trapezoidal method (10%) with the same sub-intervals. More rectangles give
a better approximation.
Let us derive a formula for the midpoint method based on n rectangles of equal
width:
ſº-ſº-ſº ſº
** (*). M (→)+...+ n’ (==)
2 2 2 •
(3.19)
30 + X1 A 1 + x2 xn−1 + xn
as h
( (*)+/(*)+...+(−))
- - - - — I
(3.20)
I .
where xj = (a + #) + ih.
3.3 The Composite Midpoint Method 67
3.3.2 Implementation
We follow the advice and lessons learned from the implementation of the trape
zoidal method and make a function midpoint(f, a, b, n) (in a file midpoint.
py) for implementing the general formula (3.21):
result *= h
return result
in contrast
TheWeerror
canintest
toour
0.2
the
particular
for
function
the trapezoidal
problem
as we explainedR
01rule. dt
3t2et3This
forwith
the
is similar trapezoidal method.
four intervals is now about 0.1
in fact not accidental: one can
show mathematically that the error of the midpoint method is a bit smaller than for
the trapezoidal method. The differences are seldom of any practical importance,
and on a laptop we can easily use n D 106 and get the answer with an error of about
10 12 in a couple of seconds.
The next example shows how easy we can combine the trapezoidal and
midpointfunctions to make a comparison of the two methods in the file compare_
integration_methods.py:
g = lambda y: exp(-y**2)
a = 0
b = 2
print ’ n midpoint trapezoidal’
for i in range(1, 21):
n = 2**i
m = midpoint(g, a, b, n)
t = trapezoidal(g, a, b, n)
print ’%7d %.16f %.16f’ % (n, m, t)
Note the efforts put into nice formatting – the output becomes
n midpoint trapezoidal
2 0.8842000076332692 0.8770372606158094
4 0.8827889485397279 0.8806186341245393
8 0.8822686991994210 0.8817037913321336
16 0.8821288703366458 0.8819862452657772
68 3 Computing Integrals
32 0.8820933014203766 0.8820575578012112
64 0.8820843709743319 0.8820754296107942
128 0.8820821359746071 0.8820799002925637
256 0.8820815770754198 0.8820810181335849
512 0.8820814373412922 0.8820812976045025
1024 0.8820814024071774 0.8820813674728968
2048 0.8820813936736116 0.8820813849400392
4096 0.8820813914902204 0.8820813893068272
8192 0.8820813909443684 0.8820813903985197
16384 0.8820813908079066 0.8820813906714446
32768 0.8820813907737911 0.8820813907396778
65536 0.8820813907652575 0.8820813907567422
131072 0.8820813907631487 0.8820813907610036
262144 0.8820813907625702 0.8820813907620528
524288 0.8820813907624605 0.8820813907623183
1048576 0.8820813907624268 0.8820813907623890
A visual inspection of the numbers shows how fast the digits stabilize in both meth
ods. It appears that 13 digits have stabilized in the last two rows.
Remark
The trapezoidal and midpoint methods are just two examples in a jungle of nu
merical integration rules. Other famous methods are Simpson’s rule and Gauss
quadrature. They all work in the same way:
bZ
a f.x/dx 1Xn
wif.xi/:
iD0
3.4 Testing
Testing of the programs for numerical integration has so far employed two strate
gies. If we have an exact answer, we compute the error and see that increasing
n decreases the error. When the exact answer is not available, we can (as in the
comparison example in the previous section) look at the integral values and see that
they stabilize as n grows. Unfortunately, these are very weak test procedures and
not at all satisfactory for claiming that the software we have produced is correctly
implemented.
To see this, we can introduce a bug in the application function that calls
trapezoidal: instead of integrating 3t2et3, we write “accidentally” 3t3et3, but
keep the same anti-derivative V.t/et3 for computing the error. With the bug and
3.4 Testing 69
n D 4, the error is 0.1, but without the bug the error is 0.2! It is of course com
pletely impossible to tell if 0.1 is the right value of the error. Fortunately, increasing
n shows that the error stays about 0.3 in the program with the bug, so the test pro
cedure with increasing n and checking that the error decreases points to a problem
in the code.
Let us look at another bug, this time in the mathematical algorithm: instead
of computing
0.5*f(a)
goes like 1400,
+ f(b).
107,
12.f .a/10,
The
Crespectively,
f.b//
error for
as n
weDwhich
should,
4;40;400
looks
we promising.
forget
when computing
the second
The problem
R121:11:9a3t2et
ndiswrite
3dt
that
the right errors should be 369, 4.08, and 0.04. That is, the error should be reduced
faster in the correct than in the buggy code. The problem, however, is that it is
reduced in both codes, and we may stop further testing and believe everything is
correctly implemented.
Unit testing
A good habit is to test small pieces of a larger code individually, one at a time.
This is known as unit testing. One identifies a (small) unit of the code, and then
one makes a separate test for this unit. The unit test should be stand-alone in
the sense that it can be run without the outcome of other tests. Typically, one
algorithm in scientific programs is considered as a unit. The challenge with unit
tests in numerical computing is to deal with numerical approximation errors.
A fortunate side effect of unit testing is that the programmer is forced to use
functions to modularize the code into smaller, logical pieces.
There are three serious ways to test the implementation of numerical methods via
unit tests:
Hand-computedR
01 v.t/, v.t/ D 3t2et3:
results Let us use two trapezoids and compute the integral
2h.v.0/ C v.0:5//C 1
1 2h.v.0:5/ C v.1// D 2:463642041244344;
when h D 0:5 is the width of the two trapezoids. Running the program gives exactly
the same result.
70 3 Computing Integrals
Solving a problem without numerical errors The best unit tests for numerical
algorithms involve mathematical problems where we know the numerical result be
forehand. Usually, numerical results contain unknown approximation errors, so
knowing the numerical result implies that we have a problem where the approx
imation errors vanish. This feature may be present in very simple mathematical
problems. For example, the trapezoidal method is exact for integration of linear
functions f.x/ D ax C b. We can therefore pick some linear function and con
struct a test function that checks equality between the exact analytical expression
for the integral and the number computed by the implementation of the trapezoidal
method.
interval
A specific
Œ1:2; 4:4
test case
and an
can“arbitrary”
be R
1:24:4.6x
linear
4/dx.
function
This f.x/
integral
D involves
6x an “arbitrary”
4. By “arbitrary”
we mean expressions where we avoid the special numbers 0 and 1 since these have
special properties in arithmetic operations (e.g., forgetting to multiply is equivalent
to multiplying by 1, and forgetting to add is equivalent to adding 0).
Ei D Cnri; (3.22)
Ei1 D Cnri 1: (3.23)
3.4 Testing 71
These are two equations for two unknowns C and r. We can easily eliminate C by
dividing the equations by each other. Then solving for r gives
ln.Ei=Ei 1/
ri1 D ln.ni : (3.24)
=ni1/
The test procedures above lead to comparison of numbers for checking that calcu
lations were correct. Such comparison is more complicated than what a newcomer
might think. Suppose we have a calculation a + b and want to check that the result
is what we expect. We start with 1 + 2:
>>> a = 1; b = 2; expected = 3
>>> a + b == expected
True
So why is 0:1C0:2 ¤ 0:3? The reason is that real numbers cannot in general be
exactly represented on a computer. They must instead be approximated by a float
ing-point number3 that can only store a finite amount of information, usually about
17 digits of a real number. Let us print 0.1, 0.2,0.1 + 0.2, and 0.3 with 17 decimals:
We see that all of the numbers have an inaccurate digit in the 17th decimal place.
Because 0.1 C 0.2 evaluates to 0.30000000000000004 and 0.3 is represented as
0.29999999999999999, these two numbers are not equal. In general, real numbers
in Python have (at most) 16 correct decimals.
When we compute with real numbers, these numbers are inaccurately repre
sented on the computer, and arithmetic operations with inaccurate numbers lead
to small rounding errors in the final results. Depending on the type of numerical
algorithm, the rounding errors may or may not accumulate.
3https://en.wikipedia.org/wiki/Floating_point
72 3 Computing Integrals
If we cannot make tests like 0.1 + 0.2 == 0.3, what should we then do? The
answer is that we must accept some small inaccuracy and make a test with a toler
ance. Here is the recipe:
Here we have set the tolerance for comparison to 1015, but calculating 0.3 -
(0.1 + 0.2) shows that it equals -5.55e-17, so a lower tolerance could be used
in this particular example. However, in other calculations we have little idea about
how accurate the answer is (there could be accumulation of rounding errors in more
complicated algorithms), so 1015 or 1014 are robust values. As we demonstrate
below, these tolerances depend on the magnitude of the numbers in the calculations.
Doing an experiment with 10k C 0:3 .10k C 0:1 C 0:2/ for k D 1;:::; 10
shows that the answer (which should be zero) is around 1016k. This means that
the tolerance must be larger if we compute with larger numbers. Setting a proper
tolerance therefore requires some experiments to see what level of accuracy one
can expect. A way out of this difficulty is to work with relative instead of absolute
differences. In a relative difference we divide by one of the operands, e.g.,
Computing this c for various k shows a value around 1016. A safer procedure is
thus to use relative differences.
Python has several frameworks for automatically running and checking a potentially
very large number of tests for parts of your software by one command. This is an
extremely useful feature during program development: whenever you have done
some changes to one or more files, launch the test command and make sure nothing
is broken because of your edits.
The test frameworks nose and py.test are particularly attractive as they are
very easy to use. Tests are placed in special test functions that the frameworks can
recognize and run for you. The requirements to a test function are simple:
def test_add()
expected = 1 + 1
computed = add(1, 1)
assert computed == expected, ’1+1=%g’ % computed
Test functions can be in any program file or in separate files, typically with names
starting with test. You can also collect tests in subdirectories: running py.test
-s -v will actually run all tests in all test*.py files in all subdirectories, while
nosetests -s -v restricts the attention to subdirectories whose names start with
test or end with _test or _tests.
As long as we add integers, the equality test in the test_add function is appro
priate, but if we try to call add(0.1, 0.2) instead, we will face the rounding error
problems explained in Sect. 3.4.3, and we must use a test with tolerance instead:
def test_add()
expected = 0.3
computed = add(0.1, 0.2)
tol = 1E-14
diff = abs(expected - computed)
assert diff < tol, ’diff=%g’ % diff
Below we shall write test functions for each of the three test procedures we
suggested: comparison with hand calculations, checking problems that can be ex
actly solved, and checking convergence rates. We stick to testing the trapezoidal
integration code and collect all test functions in one common file by the name
test_trapezoidal.py.
Hand-computed numerical results Our previous hand calculations for two trape
zoids can be checked against the trapezoidal function inside a test function (in
a file test_trapezoidal.py):
def test_trapezoidal_one_exact_result():
"""Compare one hand-computed result."""
from math import exp
v = lambda t: 3*(t**2)*exp(t**3)
n = 2
computed = trapezoidal(v, 0, 1, n)
expected = 2.463642041244344
error = abs(expected - computed)
tol = 1E-14
success = error < tol
msg = ’error=%g > tol=%g’ % (errror, tol)
assert success, msg
74 3 Computing Integrals
Note the importance of checking err against exact with a tolerance: rounding
errors from the arithmetics inside trapezoidal will not make the result exactly
like the hand-computed one. The size of the tolerance is here set to 1014, which is
a kind of all-round value for computations with numbers not deviating much from
unity.
corresponding
is
Solving
exact for
a problem
linear
testintegrands.
function
withoutfornumerical
Choosing
this unitthe
test
errors
integral
mayWelook
R1:2
know
like
4:4 .6xthat4/dx
the trapezoidal
as test case,rule
the
def test_trapezoidal_linear():
"""Check that linear functions are integrated exactly."""
f = lambda x: 6*x - 4
F = lambda x: 3*x**2 - 4*x # Anti-derivative
a = 1.2; b = 4.4
expected = F(b) - F(a)
tol = 1E-14
for n in 2, 20, 21:
computed = trapezoidal(f, a, b, n)
error = abs(expected - computed)
success = error < tol
msg = ’n=%d, err=%g’ % (n, error)
assert success, msg
for i D 0; 1;2;:::; q
– ni D 2iC1
– Compute integral with ni intervals
– Compute the error Ei
– Estimate ri from (3.24) if i>0
return r
3.4 Testing 75
def test_trapezoidal_conv_rate():
"""Check empirical convergence rates against the expected -2."""
within
Running
two decimals.
the test shows
This observation
that all ri, except
suggestthe
a tolerance
first one,ofequal
10 the target limit 2
2.
1. Before you start working with files, make sure you have the latest version of
them by running git pull.
2. Edit files, remove or create files (new files must be registered by git add).
3. When a natural piece of work is done, commit your changes by the git
commit command.
4. Implement your changes also in the cloud by doing git push.
A nice feature of Git is that people can edit the same file at the same time and
very often Git will be able to automatically merge the changes (!). Therefore,
version control is crucial when you work with others or when you do your work
on different types of computers. Another key feature is that anyone can at any
time view the history of a file, see who did what when, and roll back the entire
file collection to a previous commit. This feature is, of course, fundamental for
reliable work.
5http://hplgit.github.io/teamods/bitgit/Langtangen_bitgit-bootstrap.html
4https://en.wikipedia.org/wiki/Git_(software)
76 3 Computing Integrals
3.5 Vectorization
The functions midpoint and trapezoid usually run fast in Python and compute
an integral to a satisfactory precision within a fraction of a second. However, long
loops in Python may run slowly in more complicated implementations. To increase
the speed, the loops can be replaced by vectorized code. The integration functions
constitute a simple and good example to illustrate how to vectorize loops.
We have already seen simple examples on vectorization in Sect. 1.4 when we
could evaluate a mathematical function f.x/ for a large number of x values stored
in an array. Basically, we can write
def f(x):
return exp(-x)*sin(x) + 5*x
The result y is the array that would be computed if we ran a for loop over the
individual x values and called f for each value. Vectorization essentially eliminates
this loop in Python (i.e., the looping over x and application off to each x value are
instead performed in a library with fast, compiled code).
Vectorizing the midpoint rule The aim of vectorizing the midpoint and
trapezoidal functions is also to remove the explicit loop in Python. We start
with vectorizing the midpoint function since trapezoid is not equally straight
forward. The fundamental ideas of the vectorized algorithm are to
The
1. That
evaluation
is, n uniformly
points indistributed
the midpoint
coordinates
method are
between
xi D aC.i
a Ch=2
C 12and
/h, ibD h=2.
0;:::;n
Such
with
Let
the
uscode
test the
above
code
has
interactively
the name integration_methods_vec.py
in a Python shell to compute R01
3t2dt.
and isThe
a valid
file
Note the necessity to use exp from numpy: our v function will be called with x as
an array, and the exp function must be capable of working with an array.
The vectorized code performs all loops very efficiently in compiled code, result
ing in much faster execution. Moreover, many readers of the code will also say that
the algorithm looks clearer than in the loop-based implementation.
Vectorizing the trapezoidal rule We can use the same approach to vectorize the
trapezoid function. However, the trapezoidal rule performs a sum where the end
points have different weight. If we do sum(f(x)), we get the end points f(a) and
f(b) with weight unity instead of one half. A remedy is to subtract the error from
sum(f(x)): sum(f(x)) - 0.5*f(a) - 0.5*f(b). The vectorized version of
the trapezoidal method then becomes
Now that we have created faster, vectorized versions of functions in the previous
section, it is interesting to measure how much faster they are. The purpose of the
present section is therefore to explain how we can record the CPU time consumed
by a function so we can answer this question. There are many techniques for mea
suring the CPU time in Python, and here we shall just explain the simplest and most
convenient one: the %timeit command in IPython. The following interactive ses
sion should illustrate a competition where the vectorized versions of the functions
are supposed to win:
We see that the vectorized version is about 20 times faster: 379ms versus 8.17s.
The results for the trapezoidal method are very similar, and the factor of about 20 is
independent of the number of intervals.
bZ dZ
f.x;y/dydx;
a c
bZ dZ bZ dZ
bZ dZ
can
Let
introduce
be use
us discretized
ny
theintervals
midpoint
by any
onmethod
Œc;d
numerical
with
(3.21)
integration
length
and hy.
startrule
The
withfor
midpoint
g.x/
an integral
Drule
Rcd ffor
in.x;
one
this
y/dy.
variable.
We
integral
then becomes
g.x/ D dZ nXy1
The expression looks somewhat different from (3.21), but that is because of the
notation: since we integrate in the y direction and will have to work with both x
and y as coordinates, we must use ny for n, hy for h, and the counter i is more
naturally called j when integrating in y. Integrals in the x direction will use hy and
ny for h and n, and i as counter.
The double integral is ſº g(x).dx, which can be approximated by the midpoint
method:
b
ny
3. –1 1
| gods ~ h, X
Cl i =0
g(x), x = a + 5h, + ih.
Putting the formulas together, we arrive at the composite midpoint method for a dou
ble integral:
b d nx–1 ny-1
| | fººdyas ºn X | X ſay)
(1 C i =0 j =0
nx–1 ny-l h h
- … - y -
-ºxy ( ; *…**) -
(3.25)
Direct derivation The formula (3.25) can also be derived directly in the two
dimensional case by applying the idea of the midpoint method. We divide the
rectangle [a,b] × [c,d] into n x x ny equal-sized cells. The idea of the midpoint
method is to approximate f by a constant over each cell, and evaluate the constant
at the midpoint. Cell (i, j) occupies the area
Programming a double sum The formula (3.25) involves a double sum, which
is normally implemented as a double for loop. A Python function implementing
(3.25) may look like
that
some
If this
the
integral,
function
functione.g.,
iscomputes
storedR
23 R02in
.2xCy/dxdy
the
a module
right number:
fileDmidpoint_double.py,
9 in an interactive shell we
andcan
demonstrate
compute
result *= h
return result
from Sect. 3.3.2 “twice”? The answer is yes, if we think as we did in the mathemat
ics: compute the double integral as a midpoint rule for integrating g.x/ and define
g.xi/ in terms of a midpoint rule over f in the y coordinate. The corresponding
function has very short code:
Verification via test functions How can we test that our functions for the dou
ble integral work? The best unit test is to find a problem where the numerical
approximation error vanishes because then we know exactly what the numerical
answer should be. The midpoint rule is exact for linear functions, regardless of how
many subinterval we use. Also, any linear two-dimensional function f.x;y/ D
px C qy C r will be integrated exactly by the two-dimensional midpoint rule. We
may pick f.x;y/ D 2xCy and create a proper test function that can automatically
verify our two alternative implementations of the two-dimensional midpoint rule.
To compute the integral of f.x;y/ we take advantage of SymPy to eliminate the
possibility of errors in hand calculations. The test function becomes
3.7 Double and Triple Integrals 81
a = 0; b = 2; c = 2; d = 3
import sympy
x, y = sympy.symbols(’x y’)
I_expected = sympy.integrate(f(x, y), (x, a, b), (y, c, d))
# Test three cases: nx < ny, nx = ny, nx > ny
for nx, ny in (3, 5), (4, 4), (5, 3):
I_computed1 = midpoint_double1(f, a, b, c, d, nx, ny)
I_computed2 = midpoint_double2(f, a, b, c, d, nx, ny)
tol = 1E-14
#print
assert I_expected,
abs(I_computed1
I_computed1,
- I_expected)
I_computed2
< tol
The trapezoidal method can be used as alternative for the midpoint method. The
derivation of a formula for the double integral and the implementations follow ex
actly the same ideas as we explained with the midpoint method, but there are more
terms to write in the formulas. Exercise 3.13 asks you to carry out the details.
That exercise is a very good test on your understanding of the mathematical and
programming ideas in the present section.
c fZ
bZ dZ
g.x;y;z/dzdydx
a e
82 3 Computing Integrals
and want to approximate the integral by a midpoint rule. Following the ideas for
the double integral, we split this integral into one-dimensional integrals:
p(x, y) = ſ g(x, y, z) dz
d
q(x) = | p(x,y)ay
C
b d f b
| | | gººd-dyas = ſnous Cl
f n2–1
p(x, y) = | gººds ~ X
e k=0
g(x, y, z),
d ny-1
wo-ſpeºd, e X pay)
C j=0
b d f b nx–1
where
1 1 - 1 -
b d f
| gººd-dyas
nx–1 ny-l n-1
~ hyh, he XXX X g (a }, + ihy, c + }, + j hy, e -- }. +k)
+ -
i =0 j =0 k=0
(3.26)
Note that we may apply the ideas under Direct derivation at the end of Sect. 3.7.1
to arrive at (3.26) directly: divide the domain into n, x ny x n = cells of volumes
hyhyh: ; approximate g by a constant, evaluated at the midpoint (x, y, z), in each
cell; and sum the cell integrals hyhyh-g(x, y, zº).
3.7 Double and Triple Integrals 83
Implementation We follow the ideas for the implementations of the midpoint rule
for a double integral. The corresponding functions are shown below and found in
the file midpoint_triple. py.
def q(x):
return midpoint (lambda y : p(x, y), c, d, ny)
a = 0; b = 2; c = 2; d = 3; e = -1; f = 2
import sympy
x, y, z = sympy. symbols ('x y z’)
I_expected = sympy. integrate (
g(x, y, z), (x, a, b), (y, c, d), (z, e, f))
for nx, ny, nz in (3, 5, 2), (4, 4, 4), (5, 3, 6):
I_computed1 = midpoint_triple1(
g, a, b, c, d, e, f, nx, ny, nz)
I_computed2 = midpoint_triple2(
g, a, b, c, d, e, f, nx, ny, nz)
tol = 1E-14
print I_expected, I_computed1, I_computed2
assert abs(I_computed1 - I_expected) < tol
assert abs(I_computed2 - I_expected) < tol
if name__ == "__main__":
test midpoint_triple ()
84 3 Computing Integrals
Repeated use of one-dimensional integration rules to handle double and triple inte
grals constitute a working strategy only if the integration domain is a rectangle or
box. For any other shape of domain, completely different methods must be used.
A common approach for two- and three-dimensional domains is to divide the do
main into many small triangles or tetrahedra and use numerical integration methods
for each triangle or tetrahedron. The overall algorithm and implementation is too
complicated to be addressed in this book. Instead, we shall employ an alternative,
very simple and general method, called Monte Carlo integration. It can be im
plemented in half a page of code, but requires orders of magnitude more function
evaluations in double integrals compared to the midpoint rule.
However, Monte Carlo integration is much more computationally efficient than
the midpoint rule when computing higher-dimensional integrals in more than three
variables over hypercube domains. Our ideas for double and triple integrals can
easily be generalized to handle an integral in m variables. A midpoint formula then
involves m sums. With n cells in each coordinate direction, the formula requires
nm function evaluations. That is, the computational work explodes as an exponen
tial function of the number of space dimensions. Monte Carlo integration, on the
other hand, does not suffer from this explosion of computational work and is the
preferred method for computing higher-dimensional integrals. So, it makes sense
in a chapter on numerical integration to address Monte Carlo methods, both for
handling complex domains and for handling integrals with many variables.
integral
TheR
ab f.x/dx
Monte
Rab f.x/dx
is
Carlo
to use
integration
the mean-value
algorithm
theorem
Thefrom
ideacalculus,
of Montewhich
Carlostates
integration
that the
of
˝ D f.x;y/jg.x;y/ 0g
That is, points .x;y/ for which g.x;y/ 0 lie inside ˝, and points for which
g.x;y/ < ˝ are outside ˝. The boundary of the domain @˝ is given by the im
plicit curve g.x;y/ D 0. Such formulations of geometries have been very common
during the last couple of decades, and one refers to g as a level-set function and the
3.7 Double and Triple Integrals 85
Note that A.R/ is trivial to compute since R is a rectangle, while A.˝/ is unknown.
However, if we assume that the fraction of A.R/ occupied by A.˝/ is the same as
the fraction of random points inside ˝, we get a simple estimate for A.˝/.
To get an idea of the method, consider a circular domain ˝ embedded in a rect
angle as shown below. A collection of random points is illustrated by black dots.
import numpy as np
return area*f_mean
Verification A simple test case is to check the area of a rectangle Œ0; 2 Œ3; 4:5
embedded in a rectangle Œ0; 3 Œ2; 5. The right answer is 3, but Monte Carlo
integration is, unfortunately, never exact so it is impossible to predict the output of
the algorithm. All we know is that the estimated integral should approach 3 as the
number of random points goes to infinity. Also, for a fixed number of points, we
can run the algorithm several times and get different numbers that fluctuate around
the exact value, since different sample points are used in different calls to the Monte
Carlo integration algorithm.
thisThe
casearea
weof
identify
the rectangle
f.x;y/canD 1,beand
computed
the g function
by the integral
can be specified
R02 R
34:5 dydx,
as (e.g.)
so in
1
if .x;y/ is inside Œ0; 2 Œ3;4:5 and 1 otherwise. Here is an example on how
we can utilize the MonteCarlo_double function to compute the area for different
number of samples:
>>> def
fromg(x,
MC_double
y): import MonteCarlo_double
... return (1 if (0 <= x <= 2 and 3 <= y <= 4.5) else -1)
...
>>> MonteCarlo_double(lambda x, y: 1, g, 0, 3, 2, 5, 100)
2.9484
>>> MonteCarlo_double(lambda x, y: 1, g, 0, 3, 2, 5, 1000)
2.947032
>>> MonteCarlo_double(lambda x, y: 1, g, 0, 3, 2, 5, 1000)
3.0234600000000005
>>> MonteCarlo_double(lambda x, y: 1, g, 0, 3, 2, 5, 2000)
2.9984580000000003
>>> MonteCarlo_double(lambda x, y: 1, g, 0, 3, 2, 5, 2000)
3.1903469999999996
>>> MonteCarlo_double(lambda x, y: 1, g, 0, 3, 2, 5, 5000)
2.986515
We see that the values fluctuate around 3, a fact that supports a correct implemen
tation, but in principle, bugs could be hidden behind the inaccurate answers.
It is mathematically known that the standard deviation of the Monte Carlo es
timate of an integral converges as n1=2, where n is the number of samples. This
3.7 Double and Triple Integrals 87
kind of convergence rate estimate could be used to verify the implementation, but
this topic is beyond the scope of this book.
Test function for function with random numbers To make a test function, we
need a unit test that has identical behavior each time we run the test. This seems
difficult when random numbers are involved, because these numbers are different
every time we run the algorithm, and each run hence produces a (slightly) different
result. A standard way to test algorithms involving random numbers is to fix the
seed of the random number generator. Then the sequence of numbers is the same
every time we run the algorithm. Assuming that the MonteCarlo_doublefunction
works, we fix the seed, observe a certain result, and take this result as the correct
result. Provided the test function always uses this seed, we should get exactly this
result every time the MonteCarlo_doublefunction is called. Our test function can
then be written as shown below.
def test_MonteCarlo_double_rectangle_area():
"""Check the area of a rectangle."""
x0 = 0; x1 = 3; y0 = 2; y1 = 5 # embedded rectangle
n = 1000
np.random.seed(8) # must fix the seed!
I_expected = 3.121092 # computed with this seed
I_computed
lambda x, y: 1, g, x0, x1, y0, y1, n)
= MonteCarlo_double(
Integral over a circle The test above involves a trivial function f.x;y/ D 1. We
be
should
a circle
alsoattest
theaorigin
non-constant
with radius
f function
2, and let
and
f aDmorep complicated domain. Let ˝
possible toRcompute an exact result: in polar coordinates, x2 CRy2.
˝ f.x;y/dxdy
This choice makes it
simpli
that to
fies fluctuate r2dr
2 02 around Dthis
16 exact
=3. Weresult.
mustAs
beinprepared
the test for
casequite
above,
crude
weapproximations
experience bet
ter results with larger number of points. When we have such evidence for a working
implementation, we can turn the test into a proper test function. Here is an example:
def test_MonteCarlo_double_circle_r():
"""Check the integral of r over a circle with radius 2."""
print
x0 = -2;
’Exact
x1 = 2; y0 = -2;
integral:’, y1 = 2
I_exact.evalf()
n = 1000
np.random.seed(6)
I_expected = 16.7970837117376384 # Computed with this seed
I_computed
lambda = y: np.sqrt(x**2 + y**2),
x,MonteCarlo_double(
3.8 Exercises
We consider
Exercise 3.4:integrating
Hand-calculations
the sine function:
with sineR0bintegrals
sin.x/dx.
a) Let b D and use two intervals in the trapezoidal and midpoint method.
Compute the integral by hand and illustrate how the two numerical methods
approximates the integral. Compare with the exact value.
b) Do a) when b D 2.
Filename: integrate_sine.pdf.
useful? How does the convergence rate test behave? Any need for adjustment?
Filename: test_trapezoidal2.py.
Exercise 3.7: Write test functions for R04 p
xdx
We want
Two of the
to test
tests
how
in the
test_trapezoidal.py
trapezoidal functionare
works
meaningful
for the integral
for thisR p
xdx.
04integral.
Compute by hand the result of using 2 or 3 trapezoids and modify the test_
trapezoidal_one_exact_result function accordingly. Then modify test_
trapezoidal_conv_rateto handle the square root integral.
Filename: test_trapezoidal3.py.
Remarks The convergence rate test fails. Printing out r shows that the actual con
vergence rate for this integral is 1:5 and not 2. The reason is that the error in the
of
f.x/ px,
trapezoidal
the D
error. method6
Runningis
f00. / !
a test
.b
1with
asa/3n
a>0,
!2f00./
0, say
pointingR
for
0:14 some (unknown) 2 Œa;b. With
xdx
to a potential
shows that
problem
the convergence
in the size
p
rate is indeed restored to 2.
Fig. 3.4 Illustration of the rectangle method with evaluating the rectangle height by either the left
or right point
6http://en.wikipedia.org/wiki/Trapezoidal_rule#Error_analysis
90 3 Computing Integrals
a) Write
theintegral
an value
a function
ofRheight,
ab f.x/dx
rectangle(f,
which
by the
is a, b, n, height=’left’) for computing
rectangle
either left,
method
right,
with
orheight
mid. computed based on
b) Write three test functions for the three unit test procedures described in
Sect. 3.4.2. Make sure you test for height equal to left, right, and mid. You
may call the midpoint function for checking the result when height=mid.
Hint It may be a good idea to organize your code so that the function adaptive_
integration can be used easily in future programs you write.
a) Write a function
that implements the idea above (eps corresponds to the tolerance , and method
can be midpoint orRtrapezoidal).
on 02 x2dx and R02 pxdx
b) Test
the exact
the method
error. for D 101; 1010 and write out
c) Make a plot of n versus 2 Œ101; 1010 for R02 p
xdx. Use logarithmic scale
for .
Filename: adaptive_integration.py.
Remarks The type of method explored in this exercise is called adaptive, because
it tries to adapt the value of n to meet a given error criterion. The true error can very
seldom be computed (since we do not know the exact answer to the computational
problem), so one has to find other indicators of the error, such as the one here where
the changes in the integral value, as the number of intervals is doubled, is taken to
reflect the error.
The integrand xx does not have an anti-derivative that can be expressed in terms of
standard
convincefunctions
yourself that
(visitour
http://wolframalpha.com
claim is right. Note that
andWolfram
type integral(x**x,x)to
alpha does give you
Ij;k Z
D sin.jx/sin.kx/dx;
a) plain
Plot sin.x/sin.2x/ and 2
why you expect R sin.2x/sin.3x/
sinx sin2x dxfor
Dx0 and R ; in separatedx
plots. Ex
sin2x sin 3x D 0.
b) Use the trapezoidal rule to compute Ij;k for j D 1;:::; 10 and k D 1;:::; 10.
Filename: products_sines.py.
SN.t/ D NX
bn sin.nt/: (3.27)
nD1
We are now interested in computing the unknown coefficients bn such that SN .t/
is in some sense the best approximation to f.t/. One common way of doing this
is to first set up a general expression for the approximation error, measured by
“summing up” the squared deviation of SN from f:
E DZ
.SN .t/ f.t//2dt:
@E
D 0;
@b1
@E
D 0;
@b2
::
:
@E
D 0:
@bN
a) Compute the partial derivative @E=@b1 and generalize to the arbitrary case
@E=@bn, 1 Ä n Ä N.
b) Show that
bn D 1 Z
f.t/sin.nt/ dt:
Filename: autofit_sines.py.
Open Access This chapter is distributed under the terms of the Creative Commons Attribution
NonCommercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/),
which permits any noncommercial use, duplication, adaptation, distribution and reproduction
in any medium or format, as long as you give appropriate credit to the original author(s) and the
source, a link is provided to the Creative Commons license and any changes made are indicated.
The images or other third party material in this chapter are included in the work’s Creative
Commons license, unless indicated otherwise in the credit line; if such material is not included
in the work’s Creative Commons license and the respective action is not permitted by statutory
regulation, users will need to obtain permission from the license holder to duplicate, adapt or
reproduce the material.
Solving Ordinary Differential Equations
4
tion motivates the need for other solution methods, and we derive the Euler-Cromer
scheme1, the 2nd- and 4th-order Runge-Kutta schemes, as well as a finite difference
scheme (the latter to handle the second-order differential equation directly without
reformulating it as a first-order system). The presentation starts with undamped
free oscillations and then treats general oscillatory systems with possibly nonlinear
damping, nonlinear spring forces, and arbitrary external excitation. Besides de
veloping programs from scratch, we also demonstrate how to access ready-made
implementations of more advanced differential equation solvers in Python.
As we progress with more advanced methods, we develop more sophisticated
and reusable programs, and in particular, we incorporate good testing strategies so
that we bring solid evidence to correct computations. Consequently, the beginning
with population growth and disease modeling examples has a very gentle learning
curve, while that curve gets significantly steeper towards the end of the treatment
of differential equations for oscillatory systems.
Our first taste of differential equations regards modeling the growth of some pop
ulation, such as a cell culture, an animal population, or a human population. The
ideas even extend trivially to growth of money in a bank. Let N.t/ be the number
of individuals in the population at time t. How can we predict the evolution of N.t/
in time? Below we shall derive a differential equation whose solution is N.t/. The
equation reads
N0.t/ D rN.t/; (4.1)
where r is a number. Note that although N is an integer in real life, we model N as
a real-valued function. We are forced to do this because the solution of differential
equations are (normally continuous) real-valued functions. An integer-valued N.t/
in the model would lead to a lot of mathematical difficulties.
With a bit of guessing, you may realize that N.t/ D Cert, where C is any
number. To make this solution unique, we need to fix C, done by prescribing the
value of N at some time, usually t D 0. Say N.0/ is given as N0. Then N.t/ D
N0ert.
1The term scheme is used as synonym for method or computational recipe, especially in the con
text of numerical methods for differential equations.
4.1 Population Growth 97
It can be instructive to show how an equation like (4.1) arises. Consider some
population of (say) an animal species and let N.t/ be the number of individuals
in a certain spatial region, e.g. an island. We are not concerned with the spatial
distribution of the animals, just the number of them in some spatial area where
there is no exchange of individuals with other spatial areas. During a time interval
t, some animals will die and some new will be born. The number of deaths and
births are expected to be proportional to N. For example, if there are twice as many
individuals, we expect them to get twice as many newborns. In a time interval t,
the net growth of the population will be
we
makes
is,
where
we
double
bN.t/N
sense t,
introduce
toiswe
think
the
bexpect
Dof
number
b=N
bNthe
and
t of
proportionality
and
dNnewborns
as
d proportional
D d=Nandconstants
dN.t/
N isbN the
andnumber
dN to double
of deaths. If
too, so it
to t and “factor out” t. That
t to be proportionality constants for
newborns and deaths independent of t. Also, we introduce r D b d, which is
the net rate of growth of the population per time unit. Our model then becomes
This is called a difference equation. If we know N.t/ for some t, e.g., N.0/ D N0,
we can compute
N.t/ D N0 C t rN0;
N.2t/ D N. t/ C t rN.t/;
N.3t/ D N.2t/ C t rN.2t/;
::
:
N..kC 1/t/D N.kt/ C t rN.kt/;
where k is some arbitrary integer. A computer program can easily compute N..kC
1/t/ for us with the aid of a little loop.
Warning
Observe that the computational formula cannot be started unless we have an
initial condition!
The solution of N0 D rN is N D Cert for any constant C, and the initial
condition is needed to fix C so the solution becomes unique. However, from
a mathematical point of view, knowing N.t/ at any point t is sufficient as initial
98 4 Solving Ordinary Differential Equations
In fact, we do not need a computer since we see a repetitive pattern when doing
hand calculations, which leads us to a mathematical formula for N..kC 1/t/, :
Rather than using (4.2) as a computational model directly, there is a strong tra
dition for deriving a differential equation from this difference equation. The idea is
to consider a very small time interval t and look at the instantaneous growth as
this time interval is shrunk to an infinitesimally small size. In mathematical terms,
it means that we let t ! 0. As (4.2) stands, letting t ! 0 will just produce an
equation 0 D 0, so we have to divide by t and then take the limit:
The term on the left-hand side is actually the definition of the derivative N0.t/, so
we have
N0.t/ D rN.t/;
which is the corresponding differential equation.
There is nothing in our derivation that forces the parameter r to be constant –
it can change with time due to, e.g., seasonal changes or more permanent environ
mental changes.
N0 D rN
dN
dt D rN
dN
D rdt
N
NZ tZ
dNN Drdt
N0 0
4.1 Population Growth 99
0 tZ
N D N0 exp @ 0 r.t/dt A;
which for constant r results in N D N0ert. Note that exp.t/ is the same as et.
which
method
As will
quickly
of be
separation
described
becomesoflater,
non-trivial
variables
r mustfor
then
in many
more
requires
realistic
choices
to integrate
models
of r.N/.
dependR
The on N.
NN0 N=r.N/dN,
only gener
The
There is a huge collection of numerical methods for problems like (4.2), and in
general any equation of the form u0 D f.u;t/, where u.t/ is the unknown function
in the problem, and f is some known formula of u and optionally t. For example,
f.u;t/ D ruin (4.2). We will first present a simple finite difference method solving
u0 D f.u;t/. The idea is four-fold:
1. Introduce a mesh
u at the mesh in time
points and Nt
tn, with C1 points
introduce un tas t;:::;t
0;t1 he numerical
Nt. We seek the unknown
approximation to
u.tn/, see Fig. 4.1.
2. Assume that the differential equation is valid at the mesh points.
3. Approximate derivatives by finite differences, see Fig. 4.2.
4. Formulate a computational algorithm that can compute a new value un based on
previously computed values ui, i<n.
An example will illustrate the steps. First, we introduce the mesh, and very
often the mesh is uniform, meaning that the spacing between points tn and tnC1 is
constant. This property implies that
tn D n t; n D 0; 1;:::; Nt :
Second, the differential equation is supposed to hold at the mesh points. Note that
this is an approximation, because the differential equation is originally valid at all
real values of t. We can express this property mathematically as
u0.tn/ D f.un;tn/; n D 0; 1;:::; Nt :
For example, with our model equation u0 D ru, we have the special case
u0.tn/ D run; n D 0; 1;:::; Nt;
or
u0.tn/ D r.tn/un; n D 0; 1;:::; Nt;
if r depends explicitly on t.
100 4 Solving Ordinary Differential Equations
Instead of going to the limit t ! 0 we can use a small t, which yields a com
putable approximation to u0.tn/:
unC1 un
u0.tn/ t :
Providedby
solution wefirst
havecomputing
a known starting
u1 fromvalue,
u0, then
u0 u2
DU from u1,can
0, we u3use
from u2,toand
(4.5) advance
so forth.
the
Such an algorithm is called a numerical scheme for the differential equation and
often written compactly as
unC1 D un C tf .un;tn/; u0 D U0; n D 0; 1;:::; Nt 1: (4.6)
This scheme is known as the Forward Euler scheme, also called Euler’s method.
In our special population growth model, we have
unC1 D un C t run; u0 D U0; n D 0; 1;:::; Nt 1: (4.7)
We may also write this model using the problem-specific symbol N instead of the
generic u function:
NnC1 D Nn C t rNn; N0 D N0; n D 0; 1;:::; Nt 1: (4.8)
The observant reader will realize that (4.8) is nothing but the computational
model (4.2) arising directly in the model derivation. The formula (4.8) arises,
however, from a detour via a differential equation and a numerical method for the
differential equation. This looks rather unnecessary! The reason why we bother to
102 4 Solving Ordinary Differential Equations
Fig. 4.3 The numerical solution at points can be extended by linear segments between the mesh
points
derive the differential equation model and then discretize it by a numerical method
is simply that the discretization can be done in many ways, and we can create
(much) more accurate and more computationally efficient methods than (4.8) or
(4.6). This can be useful in many problems! Nevertheless, the Forward Euler
scheme is intuitive and widely applicable, at least when t is chosen to be small.
Let us compute (4.8) in a program. The input variables are N0, t, r, and Nt. Note
values Nn,
1;:::;N
that weare needed
need in an array
to compute Nt representation
C 1 new values of N n D 0;:::;N
NtC1. At C
total
1. of Nt C 2
N[0] = N_0
for n in range(N_t+1):
N[n+1] = N[n] + r*dt*N[n]
Fig. 4.4 Evolution of a population computed with time step 0.5 month
104 4 Solving Ordinary Differential Equations
Fig. 4.5 Evolution of a population computed with time step 0.05 month
The good thing about the Forward Euler method is that it gives an understanding
of what a differential equation is and a geometrical picture of how to construct the
solution. The first idea is that we have already computed the solution up to some
time point tn. The second idea is that we want to progress the solution from tn to
tnC1 as a straight line.
TheWedifferential
know thatequation
the line must
tells us
gothe
through
slope the
of the
solution
line: at
u0.t
tnn,/i.e.,
D the point
f.un;t Dn;un/.
n/ .t run.
That is, the differential equation gives a direct formula for the further direction of
the solution curve. We can say that the differential equation expresses how the
system (u) undergoes changes at a point.
There is a general formula for a straight line y D ax C b with slope a that goes
through the point .x0;y0/: y D a.x x0/ C y0. Using this formula adapted to the
present case, and evaluating the formula for tnC1, results in
unC1 D run.tnC1 tn/ C un D un C t run;
which is nothing but the Forward Euler formula. You are now encouraged to do Ex
ercise 4.1 to become more familiar with the geometric interpretation of the Forward
Euler method.
Our previous program was just a flat main program tailored to a special differential
equation. When programming mathematics, it is always good to consider a (large)
class of problems and making a Python function to solve any problem that fits into
the class. More specifically, we will make software for the class of differential
equation problems of the form
for some given function f, and numbers U0 and T. We also take the opportunity
to illustrate what is commonly called a demo function. As the name implies, the
purpose of such a function is solely to demonstrate how the function works (not
to be confused with a test function, which does verification by use of assert).
The Python function calculating the solution must take f, U0, t, and T as in
put, find the corresponding Nt, compute the solution, and return an array with
u0;u1;:::;uN
t and an array with t0;t1;:::;tNt. The Forward Euler scheme reads
The corresponding program may now take the form (file ode_FE.py):
106 4 Solving Ordinary Differential Equations
def def
"""Test
demo_population_growth():
f(u,case:
t): u’=r*u, u(0)=100."""
return 0.1*u
if __name__ == ’__main__’:
demo_population_growth()
This program file, called ode_FE.py, is a reusable piece of code with a general
ode_FE function that can solve any differential equation u0 D f.u;t/ and a demo
function for the special case u0 D 0:1u, u.0/ D 100. Observe that the call to the
demo function is placed in a test block. This implies that the call is not active if
ode_FE is imported as a module in another program, but active if ode_FE.pyis run
as a program.
The solution should be identical to what the growth1.pyprogram produces with
the same parameter settings (r D 0:1, N0 D 100). This feature can easily be tested
by inserting a print statement, but a much better, automated verification is suggested
in Exercise 4.1. You are strongly encouraged to take a “break” and do that exercise
now.
N0 D r.N/N :
The reader is strongly encouraged to repeat the steps in the derivation of the Forward
Euler scheme and establish that we get
NnC1 D Nn C t r.Nn/Nn;
which computes as easy as for a constant r, since r.Nn/ is known when computing
NnC1. Alternatively, one can use the Forward Euler formula for the general problem
u0 D f.u;t/ and use f.u;t/ D r.u/u and replace u by N.
The simplest choice of r.N/is a linear function, starting with some growth value
Nr and declining until the population has reached its maximum, M, according to the
available resources:
r.N/ D Nr.1 N=M/:
In the beginning, N M and we will have exponential growth eNrt, but as N
increases, r.N/ decreases, and when N reaches M, r.N/ D 0 so there is now
more growth and the population remains at N.t/ D M. This linear choice of r.N/
gives rise to a model that is called the logistic model. The parameter M is known
as the carrying capacity of the population.
Let us run the logistic model with aid of the ode_FE function in the ode_FE
module. We choose N.0/ D 100, t D 0:5 month, T D 60 months, r D 0:1,
and M D 500. The complete program, called logistic.py, is basically a call to
ode_FE:
Figure 4.7 shows the resulting curve. We see that the population stabilizes
around M D 500 individuals. A corresponding exponential growth would reach
N0ert D 100e0:160 40;300 individuals!
It is always interesting to see what happens with large t values. We may set
t D 20 and T D 100. Now the solution, seen in Fig. 4.8, oscillates and is
hence qualitatively wrong, because one can prove that the exact solution of the
differential equation is monotone. (However, there is a corresponding difference
108 4 Solving Ordinary Differential Equations
equation model, NnC1 D rNn.1 Nn=M/, which allows oscillatory solutions and
those are observed in animal populations. The problem with large t is that it
just leads to wrong mathematics – and two wrongs don’t make a right in terms of
a relevant model.)
4.1 Population Growth 109
How can we verify that the programming of an ODE model is correct? The best
method is to find a problem where there are no unknown numerical approximation
errors, because we can then compare the exact solution of the problem with the re
sult produced by our implementation and expect the difference to be within a very
small tolerance. We shall base a unit test on this idea and implement a correspond
ing test function (see Sect. 3.4.4) for automatic verification of our implementation.
It appears that most numerical methods for ODEs will exactly reproduce a solu
tion u that is linear in t. We may therefore set u D at Cb and choose any f whose
derivative is a. The choice f.u;t/ D a is very simple, but we may add anything
that is zero, e.g.,
f.u;t/ D a C .u .at C b//m:
This is a valid f.u;t/ for any a, b, and m. The corresponding ODE looks highly
non-trivial, however:
u0 D a C .u .at C b//m:
Using the general ode_FE function in ode_FE.py, we may write a proper test
function as follows (in file test_ode_FE_exact_linear.py):
def test_ode_FE():
"""Test that a linear u(t)=a*t+b is exactly reproduced."""
def exact_solution(t):
return a*t + b
a = 4
b = -1
m = 6
3http://users.rcn.com/jkimball.ma.ultranet/BiologyPages/P/Populations.html
2http://en.wikipedia.org/wiki/Population_growth
110 4 Solving Ordinary Differential Equations
dt = 0.5
T = 20.0
Recall that test functions should start with the name test_, have no arguments, and
formulate the test as a boolean expression success that is True if the test passes
and
success
Falsecanif italso
fails.
beTest
a boolean
functions
expression
should make
as in assert
the test asdiff
assert
< tol).
success(here
Our aim with this section is to show in detail how one can apply mathematics and
programming to investigate spreading of diseases. The mathematical model is now
a system of three differential equations with three unknown functions. To derive
such a model, we can use mainly intuition, so no specific background knowledge of
diseases is required.
Imagine a boarding school out in the country side. This school is a small and closed
society. Suddenly, one or more of the pupils get a flu. We expect that the flu may
spread quite effectively or die out. The question is how many of the pupils and
the school’s staff will be affected. Some quite simple mathematics can help us to
achieve insight into the dynamics of how the disease spreads.
Let the mathematical function S.t/ count how many individuals, at time t, that
have the possibility to get infected. Here, t may count hours or days, for instance.
These individuals make up a category called susceptibles, labeled as S. Another
category, I, consists of the individuals that are infected. Let I.t/ count how many
there are in category I at time t. An individual having recovered from the disease
is assumed to gain immunity. There is also a small possibility that an infected will
die. In either case, the individual is moved from the I category to a category we call
the removed category, labeled with R. We let R.t/ count the number of individuals
in the R category at time t. Those who enter the R category, cannot leave this
category.
4.2 Spreading of Diseases 111
We can use mathematics to more precisely describe the exchange between the
categories. The fundamental idea is to describe the changes that take place during
a small time interval, denoted by t.
Our disease model is often referred to as a compartment model, where quantities
are shuffled between compartments (here a synonym for categories) according to
some rules. The rules express changes in a small time interval t, and from these
changes we can let t go to zero and obtain derivatives. The resulting equations
then go from difference equations (with finite t) to differential equations (t !
0).
at the
Wemesh
introduce
points.
a uniform
The numerical time, tn D nto t,S nat D
mesh inapproximation time
0;:::;N
tn is denoted
t , and seek S
by Sn.
Similarly, we seek the unknown values of I.t/ and R.t/ at the mesh points and
introduce a similar notation In and Rn for the approximations to the exact values
I.tn/ and R.tn/.
In the time interval t we know that some people will be infected, so S will de
crease. We shall soon argue by mathematics that there will be ˇtSI new infected
individuals in this time interval, where ˇ is a parameter reflecting how easy people
get infected during a time interval of unit length. If the loss in S is ˇtSI, we have
that the change in S is
SnC1 Sn D ˇ tSnIn: (4.9)
Dividing by t and letting t ! 0, makes the left-hand side approach S0.tn/ such
that we obtain a differential equation
S0 D ˇSI: (4.10)
The reasoning in going from the difference equation (4.9) to the differential equa
tion (4.10) follows exactly the steps explained in Sect. 4.1.1.
Before proceeding with how I and R develops in time, let us explain the formula
ˇtSI. We have S susceptibles and I infected people. These can make up SI
pairs. Now, suppose that during a time intervalT we measure that m actual pairwise
meetings do occur among n theoretically possible pairings of people from the S
and I categories. The probability that people meet in pairs during a time T is (by
the empirical frequency definition of probability) equal to m=n, i.e., the number
of successes divided by the number of possible outcomes. From such statistics we
normally derive quantities expressed per unit time, i.e., here we want the probability
per unit time, , which is found from dividing by T: D m=.nT/.
Given the probability , the expected number of meetings per time interval of
SI possible pairs of people is (from basic statistics) SI. During a time interval
t, there will be SIt expected number of meetings between susceptibles and
infected people such that the virus may spread. Only a fraction of the tSI
112 4 Solving Ordinary Differential Equations
meetings are effective in the sense that the susceptible actually becomes infected.
Counting that m people get infected in n such pairwise meetings (say 5 are infected
from 1000 meetings), we can estimate the probability of being infected as p D
m=n. The expected number of individuals in the S category that in a time interval
t catch the virus and get infected is then p tSI. Introducing a new constant
ˇ D p to save some writing, we arrive at the formula ˇ tSI.
The value of ˇ must be known in order to predict the future with the disease
model. One possibility is to estimate p and from their meanings in the derivation
above. Alternatively, we can observe an “experiment” where there are S0 suscepti
bles and I0 infected at some point in time. During a time interval T we count that N
susceptibles have become infected. Using (4.9) as a rough approximation of how S
has developed during time T (and now T is not necessarily small, but we use (4.9)
anyway), we get
N
N D ˇTS0I0 ) ˇ D : (4.11)
TS0I0
We need an additional equation to describe the evolution of I.t/. Such an equa
tion is easy to establish by noting that the loss in the S category is a corresponding
gain in the I category. More precisely,
InC1 In D ˇ tSnIn : (4.12)
However, there is also a loss in the I category because people recover from the
disease. Suppose that we can measure that m out of n individuals recover in a time
period T (say 10 of 40 sick people recover during a day: m D 10, n D 40, T D
24h). Now, D m=.nT/ is the probability that one individual recovers in a unit
time interval. Then (on average) tI infected will recover in a time interval t.
This quantity represents a loss in the I category and a gain in the R category. We
can therefore write the total change in the I category as
InC1 In D ˇ tSnIn tIn: (4.13)
The change in the R category is simple: there is always an increase from the I
category:
RnC1 Rn D tIn: (4.14)
Since there is no loss in the R category (people are either recovered and immune,
or dead), we are done with the modeling of this category. In fact, we do not strictly
need the equation (4.14) for R, but extensions of the model later will need an equa
tion for R.
Dividing by t in (4.13) and (4.14) and letting t ! 0, results in the corre
sponding differential equations
I0 D ˇSI I; (4.15)
and
R0 D I: (4.16)
To summarize, we have derived difference equations (4.9)–(4.14), and alternative
differential equations (4.15)–(4.16). For reference, we list the complete set of the
4.2 Spreading of Diseases 113
Note that we have isolated the new unknown quantities SnC1, InC1, and RnC1 on
the left-hand side, such that these can readily be computed if Sn, In, and Rn are
known. To get such a procedure started, we need to know S0, I0, R0. Obviously,
we also need to have values for the parameters ˇ and .
We also list the system of three differential equations:
S0 D ˇSI; (4.20)
I0 D ˇSI I; (4.21)
R0 D I: (4.22)
This differential equation model (and also its discrete counterpart above) is known
as an SIR model. The input data to the differential equation model consist of the
parameters ˇ and as well as the initial conditions S.0/ D S0, I.0/ D I0, and
R.0/ D R0.
Let us apply the same principles as we did in Sect. 4.1.2 to discretize the differential
equation system by the Forward Euler method. We already have a time mesh and
time-discrete quantities Sn, In, Rn, n D 0;:::;Nt. The three differential equations
are assumed to be valid at the mesh points. At the point tn we then have
for n D 0; 1;:::; Nt. This is an approximation since the differential equations are
originally valid at all times t (usually in some finite interval Œ0; T). Using forward
finite differences for the derivatives results in an additional approximation,
SnC1 Sn
t D ˇSnIn; (4.26)
InC1 t In
D ˇSnInIn; (4.27)
DIn:
RnC1 t Rn (4.28)
As we see, these equations are identical to the difference equations that naturally
arise in the derivation of the model. However, other numerical methods than the
Forward Euler scheme will result in slightly different difference equations.
114 4 Solving Ordinary Differential Equations
# Time unit: 1 h
beta = 10./(40*8*24)
gamma = 3./(15*24)
dt = 0.1 # 6 min
D = 30 # Simulate for D days
N_t = int(D*24/dt) # Corresponding no of hours
# Initial condition
S[0] = 50
I[0] = 1
R[0] = 0
fig l2,
l1, l3 = plt.plot(t, S, t, I, t, R)
= plt.figure()
fig.legend((l1,
plt.xlabel(’hours’)
l2, l3), (’S’, ’I’, ’R’), ’upper left’)
plt.show()
plt.savefig(’tmp.pdf’); plt.savefig(’tmp.png’)
This program was written to investigate the spreading of a flu at the mentioned
boarding school, and the reasoning for the specific choices ˇ and goes as follows.
At some other school where the disease has already spread, it was observed that in
the beginning of a day there were 40 susceptibles and 8 infected, while the numbers
were 30 and 18, respectively, 24 hours later. Using 1 h as time unit, we then have
from (4.11) that ˇ D 10=.408 24/. Among 15 infected, it was observed that
3 recovered during a day, giving D 3=.15 24/. Applying these parameters to
a new case where there is one infected initially and 50 susceptibles, gives the graphs
in Fig. 4.9. These graphs are just straight lines between the values at times ti D i t
as computed by the program. We observe that S reduces as I and R grows. After
about 30 days everyone has become ill and recovered again.
We can experiment with ˇ and to see whether we get an outbreak of the disease
or not. Imagine that a “wash your hands” campaign was successful and that the
other school in this case experienced a reduction of ˇ by a factor of 5. With this
4.2 Spreading of Diseases 115
lower ˇ the disease spreads very slowly so we simulate for 60 days. The curves
appear in Fig. 4.10.
Fig. 4.10 Small outbreak of a flu at a boarding school (ˇ is much smaller than in Fig. 4.9)
116 4 Solving Ordinary Differential Equations
Looking at the equation for I, it is clear that we must have ˇSI I > 0 for I to
increase. When we start the simulation it means that
or simpler
ˇS.0/
(4.29)
When we had a specific differential equation with one unknown, we quickly turned
to an abstract differential equation written in the generic form u0 D f.u;t/. We re
fer to such a problem as a scalar ODE. A specific equation corresponds to a specific
choice of the formula f.u;t/ involving u and (optionally) t.
It is advantageous to also write a system of differential equations in the same
abstract notation,
u0 D f.u; t/;
but this time it is understood that u is a vector of functions and f is also vector. We
say that u0 D f.u;t/ is a vector ODE or system of ODEs in this case. For the SIR
model we introduce the two 3-vectors, one for the unknowns,
u D .S.t/;I.t/;R.t//;
4.2 Spreading of Diseases 117
The equation u0 D f.u;t/ means setting the two vectors equal, i.e., the components
must be pairwise equal. Since u0 D .S0;I 0;R0/, we get that u0 D f implies
S0 D ˇSI;
I0 D ˇSI I;
R0 D I:
The generalized short notation u0 D f.u;t/ is very handy since we can derive
numerical methods and implement software for this abstract system and in a par
ticular application just identify the formulas in the fvector, implement these, and
call functionality that solves the differential equation system.
both in the scalar and vector case. In the vector case, u[n] is a one-dimensional
numpy array of length m C 1 holding the mathematical quantity un, and the Python
function f must return a numpy array of length m C1. Then the expression u[n] +
dt*f(u[n], t[n]) is an array plus a scalar times an array.
For all this to work, the complete numerical solution must be represented by
a two-dimensional array, created by u = zeros((N_t+1, m+1)). The first index
counts the time points and the second the components of the solution vector at one
use
timeonly
point.
oneThat
index,
is, as
u[n,i]
in u[n],
corresponds
this is the to
same
the as
mathematical
u[n,:] andquantity
picks out
uniall
. When we
the com
ponents in the solution at the time point with index n. Then the assignment u[n+1]
= ... becomes correct because it is actually an in-place assignment u[n+1, :]
= .... The nice feature of these facts is that the same piece of Python code works
for both a scalar ODE and a system of ODEs!
The ode_FE function for the vector ODE is placed in the file ode_system_FE.
py and was written as follows:
The line f_ = lambda ... needs an explanation. For a user, who just needs to
define the fin the ODE system, it is convenient to insert the various mathematical
expressions on the right-hand sides in a list and return that list. Obviously, we
could demand the user to convert the list to a numpy array, but it is so easy to do
a general such conversion in the ode_FE function as well. To make sure that the
result from f is indeed an array that can be used for array computing in the formula
u[n] + dt*f(u[n], t[n]), we introduce a new function f_ that calls the user’s
f and sends the results through the numpy function asarray, which ensures that its
argument is converted to a numpy array (if it is not already an array).
Note also the extra parenthesis required when calling zeros with two indices.
Let us show how the previous SIR model can be solved using the new general
ode_FE that can solve any vector ODE. The user’s f(u, t) function takes a vector
u, with three components corresponding to S, I, and R as argument, along with the
current time point t[n], and must return the values of the formulas of the right-hand
sides in the vector ODE. An appropriate implementation is
Note that the S, I, and R values correspond to Sn, In, and Rn. These values are then
just inserted in the various formulas in the vector ODE. Here we collect the values
in a list since the ode_FE function will anyway wrap this list in an array. We could,
of course, returned an array instead:
The list version looks a bit nicer, so that is why we prefer a list and rather introduce
f_ = lambda u, t: asarray(f(u,t)) in the general ode_FE function.
We can now show a function that runs the previous SIR example, while using
the generic ode_FE function:
def demo_SIR():
"""Test
def f(u,case
t): using a SIR model."""
S, I, R = u
return [-beta*S*I, beta*S*I - gamma*I, gamma*I]
42 Spreading of Diseases 119
beta = 10./(40+8+24)
gamma = 3./(15+24)
dt = 0.1 # 6 min
D = 30 # Simulate for D days
N_t = int (D+24/dt) # Corresponding no of hours
T = dt kN_t # End time
U_0 = [50, 1, 0]
S = u [: ,0]
I = u [: , 1]
R = u [: , 2.]
fig = plt. figure ()
11, 12, 13 = plt. plot (t, S, t, I, t, R)
fig. legend ( (11, 12, 13), ('S', 'I', 'R'), 'lower right’)
plt. xlabel (2 hours”)
plt. show ()
# Consistency check:
N = S [O] + I [O] + R[O]
eps = 1E-12 # Tolerance for comparing real numbers
for n in range (len (S)):
SIR_sum = S [n] + I [n] + R[n]
if abs (SIR_sum - N) > eps:
print ’ººk consistency check failed: S-I*R=%g = %g? /\
(SIR_sum, N)
if name__ == "__main__":
demo_SIR ()
Recall that the u returned from ode_FE contains all components (S, I, R) in the
solution vector at all time points. We therefore need to extract the S, I, and R
values in separate arrays for further analysis and easy plotting.
Another key feature of this higher-quality code is the consistency check. By
adding the three differential equations in the SIR model, we realize that S' + I' +
R = 0, which means that S + I + R = const. We can check that this relation holds
by comparing S" + I" + R'' to the sum of the initial conditions. The check is not
a full-fledged verification, but it is a much better than doing nothing and hoping that
the computation is correct. Exercise 4.5 suggests another method for controlling the
quality of the numerical solution.
Let us now assume that immunity after the disease only lasts for some certain time
period. This means that there is transport from the R state to the S state:
* – –
120 4 Solving Ordinary Differential Equations
Modeling the loss of immunity is very similar to modeling recovery from the
disease: the amount of people losing immunity is proportional to the amount of
recovered patients and the length of the time interval t. We can therefore write
the loss in the R category as tR in time t, where 1 is the typical time it
takes to lose immunity. The loss in R.t/ is a gain in S.t/. The “budgets” for the
categories therefore become
SnC1 D Sn ˇ tSnIn C tRn; (4.30)
InC1 D In C ˇ tSnIn tIn; (4.31)
RnC1 D Rn C tIn tRn: (4.32)
S0 D ˇSI C R; (4.33)
I0 D ˇSI I; (4.34)
R0 D I R: (4.35)
This system can be solved by the same methods as we demonstrated for the original
SIR model. Only one modification in the program is necessary: adding nu*R[n] to
the S[n+1] update and subtracting the same quantity in the R[n+1] update:
for n in range(N_t):
S[n+1] = S[n] - dt*beta*S[n]*I[n] + dt*nu*R[n]
I[n+1]
R[n+1] = I[n]
R[n] + dt*beta*S[n]*I[n]
dt*gamma*I[n] - dt*nu*R[n]
- dt*gamma*I[n]
TheSetting
modified1code
to 50
is days,
found reducing
in the fileˇSIR2.py.
by a factor of 4 compared to the previous
example (ˇ D 0:00033), and simulating for 300 days gives an oscillatory behavior
in the categories, as depicted in Fig. 4.11. It is easy now to play around and study
how the parameters affect the spreading of the disease. For example, making the
disease slightly more effective (increase ˇ to 0.00043) and increasing the average
time to loss of immunity to 90 days lead to other oscillations, see Fig. 4.12.
We can extend the model to also include vaccination. To this end, it can be useful
to track those who are vaccinated and those who are not. So, we introduce a fourth
category, V, for those who have taken a successful vaccination. Furthermore, we
assume that in a time interval t, a fraction pt of the S category is subject to
a successful vaccination. This means that in the time t, ptS people leave from
the S to the V category. Since the vaccinated ones cannot get the disease, there is no
impact on the I or R categories. We can visualize the categories, and the movement
between them, as
4.2 Spreading of Diseases 121
The program needs to store V.t/ in an additional array V, and the plotting command
must be extended with more arguments to plot V versus t as well. The complete
code is found in the file SIRV1.py.
Using p D 0:0005 and p D 0:0001 as values for the vaccine efficiency pa
rameter, the effect of vaccination is seen in Fig. 4.13 (other parameters are as in
Fig. 4.11).
Fig. 4.13 The effect of vaccination: p D 0:0005 (left) and p D 0:0001 (right)
4.2 Spreading of Diseases 123
What about modeling a vaccination campaign? Imagine that six days after the out
break of the disease, the local health station launches a vaccination campaign. They
reach out to many people, say 10 times as efficiently as in the previous (constant
vaccination) case. If the campaign lasts for 10 days we can write
(
0:005; 624 Ä t Ä 15 24;
p.t/ D
0; otherwise
Note that we must multiply the t value by 24 because t is measured in hours, not
days. In the differential equation system, pS.t/ must be replaced by p.t/S.t/, and
in this case we get a differential equation system with a term that is discontinu
ous. This is usually quite a challenge in mathematics, but as long as we solve the
equations numerically in a program, a discontinuous coefficient is easy to treat.
There are two ways to implement the discontinuous coefficient p.t/: through
a function and through an array. The function approach is perhaps the easiest:
def p(t):
return 0.005 if (6*24 <= t <= 15*24) else 0
In the code for updating the arrays S and V we get a term p(t[n])*S[n].
We can also let p.t/ be an array filled with correct values prior to the simulation.
Then we need to allocate an array p of length N_t+1 and find the indices corre
sponding to the time period between 6 and 15 days. These indices are found from
the time point divided by t. That is,
p = zeros(N_t+1)
start_index = 6*24/dt
stop_index = 15*24/dt
p[start_index:stop_index] = 0.005
The p.t/S.t/ term in the updating formulas for S and V simply becomes
p[n]*S[n]. The file SIRV2.py contains a program based on filling an array p.
The effect of a vaccination campaign is illustrated in Fig. 4.14. All the data are
as in Fig. 4.13 (left), except that p is ten times stronger for a period of 10 days and
p D 0 elsewhere.
124 4 Solving Ordinary Differential Equations
Numerous engineering constructions and devices contain materials that act like
springs. Such springs give rise to oscillations, and controlling oscillations is a key
engineering task. We shall now learn to simulate oscillating systems.
As always, we start with the simplest meaningful mathematical model, which
for oscillations is a second-order differential equation:
of the body on the x axis, along which the body moves. The spring is not stretched
when x D 0, so the force is zero, and x D 0 is hence the equilibrium position of
the body. The spring force is kx, where k is a constant to be measured. We as
sume that there are no other forces (e.g., no friction). Newton’s 2nd law of motion
F D ma then has F D kx and a D Rx,
kx D mRx; (4.41)
The exact solution of (4.42) with these initial conditions is x.t/ D X0 cos!t. This
can easily be verified by substituting into (4.42) and checking the initial conditions.
The solution tells that such a spring-mass system oscillates back and forth as de
scribed by a cosine curve.
The differential equation (4.42) appears in numerous other contexts. A classical
example is a simple pendulum that oscillates back and forth. Physics books derive,
from Newton’s second law of motion, that
mLÂ00 C mg sin D 0;
where m is the mass of the body at the end of a pendulum with length L, g is the
acceleration
Considering small
of gravity, Â, sinÂ
anglesand  is the Â,
angle
and the
we get
pendulum
(4.42) with
makes
xD Â, !the
with Dvertical.p
g=L,
x.0/ D , and x0.0/ D 0, if is the initial angle and the pendulum is at rest at
t D 0.
126 4 Solving Ordinary Differential Equations
u0 D v; (4.43)
v0 D !2u: (4.44)
(Notice that we can use u00 D v0 to remove the second-order derivative from New
ton’s 2nd law.)
We can now apply the Forward Euler method to (4.43)–(4.44), exactly as we did
in Sect. 4.2.2:
unC1 t un
D vn; (4.45)
vnC1 t vn D !2un; (4.46)
A simple program for (4.47)–(4.48) follows the same ideas as in Sect. 4.2.3:
omega = 2
P = 2*pi/omega
dt = P/20
T = 3*P
N_t = int(round(T/dt))
t = linspace(0, N_t*dt, N_t+1)
u = zeros(N_t+1)
v = zeros(N_t+1)
# Initial condition
X_0 = 2
u[0] = X_0
v[0] = 0
4.3 Oscillating One-Dimensional Systems 127
fig l2
l1, = plt.figure()
= plt.plot(t, u, ’b-’, t, X_0*cos(omega*t), ’r--’)
fig.legend((l1,
plt.xlabel(’t’) l2), (’numerical’, ’exact’), ’upper left’)
plt.show()
plt.savefig(’tmp.pdf’); plt.savefig(’tmp.png’)
Fig. 4.17 Simulation of an oscillating system with different time steps. Upper left:40 steps per
oscillation period. Upper right: 160 steps per period. Lower left: 2000 steps per period. Lower
right: 2000 steps per period, but longer simulation
4.3 Oscillating One-Dimensional Systems 129
we can replace un in the last equation by the recently computed value unC1 from
the first equation:
unC1 D un C t vn; (4.49)
vnC1 D vn t !2unC1: (4.50)
Before justifying this fix more mathematically, let us try it on the previous exam
ple. The results appear in Fig. 4.18. We see that the amplitudedoes not grow, but the
phase is not entirely correct. After 40 periods (Fig. 4.18 right) we see a significant
difference between the numerical and the exact solution. Decreasing t decreases
the error. For example, with 2000 intervals per period, we only see a small phase
error even after 50,000 periods (!). We can safely conclude that the fix results in an
excellent numerical method!
Let us interpret the adjusted scheme mathematically. First we order (4.49)–(4.50)
such that the difference approximations to derivatives become transparent:
unC1 un
t D vn; (4.51)
Wehave
we vn on(4.51)
interpret the right-hand
as the differential
side. Theequation
left-handsampled
side is then
at mesh
a forward
pointdifference
tn, because
or
Forward Euler approximation to the derivative u0, see Fig. 4.2. On the other hand,
we interpret (4.52) as the differential equation sampled at mesh point tnC1, since we
Fig. 4.18 Adjusted method: first three periods (left) and period 36–40 (right)
130 4 Solving Ordinary Differential Equations
have unC1 on the right-hand side. In this case, the difference approximation on the
left-hand side is a backward difference,
v0.tnC1/ vnC1 t vn or v0.tn/ vn vn
t 1
:
Figure 4.19 illustrates the backward difference. The error in the backward differ
ence is proportional to t, the same as for the forward difference (but the propor
tionality constant in the error term has different sign). The resulting discretization
method for (4.52) is often referred to as a Backward Euler scheme.
To summarize, using a forward difference for the first equation and a backward
difference for the second equation results in a much better method than just using
forward differences in both equations.
The standard way of expressing this scheme in physics is to change the order of
the equations,
v0 D !2u; (4.53)
u0 D v; (4.54)
and apply a forward difference to (4.53) and a backward difference to (4.54):
vnC1 D vn t !2un; (4.55)
unC1 D un C t vnC1: (4.56)
That is, first the velocity v is updated and then the position u, using the most re
cently computed velocity. There is no difference between (4.55)–(4.56) and (4.49)–
(4.50) with respect to accuracy, so the order of the original differential equations
does not matter. The scheme (4.55)–(4.56) goes under the names Semi-implicit
Euler4 or Euler-Cromer. The implementation of (4.55)–(4.56) is found in the file
osc_EC.py. The core of the code goes like
4http://en.wikipedia.org/wiki/Semi-implicit_Euler_method
4.3 Oscillating One-Dimensional Systems 131
u = zeros(N_t+1)
v = zeros(N_t+1)
# Initial condition
u[0] = 2
v[0] = 0
A very popular method for solving scalar and vector ODEs of first order is the
2nd-order Runge-Kutta method (RK2), also known as Heun’s method. The idea,
first thinking of a scalar ODE, is to form a centered difference approximation to the
derivative between two time points:
unC1 un
2 t/
u0.tn C 1 :
t
The centered difference formula is visualized in Fig. 4.20. The error in the centered
difference is proportional to t2, one order higher than the forward and backward
differences, which means that if we halve t, the error is more effectively reduced
in the centered difference since it is reduced by a factor of four rather than two.
The problem with such a centered scheme for the general ODE u0 D f.u;t/ is
that we get
unC1 t un D f.unC 1
2 ;tnC 12/;
which leads to difficulties since we do not know what unC12 is. However, we can
approximate the value of f between two time levels by the arithmetic average of
This results in
unC1 un
t D1
2.f.un;tn/ C f.unC1;tnC1
//;
which in general is a nonlinear algebraic equation for unC1 if f.u;t/ is not a lin
ear function
nonlinear of u. Towe
equations, deal with
can the unknown
approximate term f.unC1;t
or predict unC1 using
nC1/,awithout
Forwardsolving
Euler
step:
unC1 D un C tf.un;tn/:
There is a jungle of methods for solving ODEs, and it would be nice to have
easy access to implementations of a wide range of methods, especially the sophis
ticated and complicated adaptive methods that adjust t automatically to obtain
a prescribed accuracy. The Python package Odespy5 gives easy access to a lot of
numerical methods for ODEs.
The simplest possible example on using Odespy is to solve u0 D u, u.0/ D 2,
for 100 time steps until t D 4:
5https://github.com/hplgit/odespy
4.3 Oscillating One-Dimensional Systems 133
import odespy
u, t = solver.solve(time_points)
In other words, you define your right-hand side function f(u, t), initialize an
Odespy solver object, set the initial condition, compute a collection of time points
where you want the solution, and ask for the solution. The returned arrays u and t
can be plotted directly: plot(t, u).
A nice feature of Odespy is that problem parameters can be arguments to the
user’s f(u, t) function. For example, if our ODE problem is u0 D au Cb, with
two problem parameters a and b, we may write our f function as
a = 2
b = 1
solver = method(f, f_args=[a, b])
This is a good feature because problem parameters must otherwise be global vari
ables – now they can be arguments in our right-hand side function in a natural way.
Exercise 4.16 asks you to make a complete implementation of this problem and plot
the solution.
Using Odespy to solve oscillation ODEs like u00 C !2u D 0, reformulated as
a system u0 D v and v0 D !2u, is done as follows. We specify a given number
of time steps per period and compute the associated time steps and end time of the
simulation (T), given a number of periods to simulate:
import odespy
The last two statements are important since our two functions u and v in the ODE
system are packed together in one array inside the Odespy solver. The solution
4.3 Oscillating One-Dimensional Systems 135
of the ODE system is returned as a two-dimensional array where the first column
(sol[:,0]) stores u and the second (sol[:,1]) stores v. Plotting u and v is
a matter of running plot(t, u, t, v).
Remark
In the right-hand side function we write f(sol, t, omega) instead of f(u,
t, omega) to indicate that the solution sent to f is a solution at time t where
the values of u and v are packed together: sol = [u, v]. We might well use u
as argument:
This just means that we redefine the name u inside the function to mean the
solution at time t for the first component of the ODE system.
To switch to another numerical method, just substitute RK2 by the proper name
of the desired method. Typing pydoc odespy in the terminal window brings up
a list of all the implemented methods. This very simple way of choosing a method
suggests an obvious extension of the code above: we can define a list of methods,
run all methods, and compare their u curves in a plot. As Odespy also contains
the Euler-Cromer scheme, we rewrite the system with v0 D !2u as the first ODE
and u0 D v as the second ODE, because this is the standard choice when using the
Euler-Cromer method (also in Odespy):
This change of equations also affects the initial condition: the first component is
zero and second is X_0 so we need to pass the list [0, X_0] to solver.set_
initial_condition.
def compare(odespy_methods,
omega,
X_0,
number_of_periods,
time_intervals_per_period=20):
legends = []
for solver in solvers:
sol, t = solver. solve (time_points)
v = sol [: ,0]
u = solſ: , 1]
A new feature in this code is the ability to plot only the last p periods, which allows
us to perform long time simulations and watch the end results without a cluttered
plot with too many periods. The syntax t [-m:] plots the last m elements in t
(a negative index in Python arrays/lists counts from the end).
We may compare Heun's method (or equivalently the RK2 method) with the
Euler-Cromer scheme:
Figure 4.22 shows how Heun's method (the blue line with small disks) has consid
erable error in both amplitude and phase already after 14–20 periods (upper left),
but using three times as many time steps makes the curves almost equal (upper
4.3 Oscillating One-Dimensional Systems 137
Fig. 4.22 Illustration of the impact of resolution (time steps per period) and length of simulation
right). However, after 194–200 periods the errors have grown (lower left), but can
be sufficiently reduced by halving the time step (lower right).
With all the methods in Odespy at hand, it is now easy to start exploring other
methods, such as backward differences instead of the forward differences used in
the Forward Euler scheme. Exercise 4.17 addresses that problem.
Odespy contains quite sophisticated adaptive methods where the user is “guar
anteed” to get a solution with prescribed accuracy. There is no mathematical guar
antee, but the error will for most cases not deviate significantly from the user’s
tolerance that reflects the accuracy. A very popular method of this type is the
Runge-Kutta-Fehlberg method, which runs a 4th-order Runge-Kutta method and
uses a 5th-order Runge-Kutta method to estimate the error so that t can be ad
justed to keep the error below a tolerance. This method is also widely known as
ode45, because that is the name of the function implementing the method in Mat
lab. We can easily test the Runge-Kutta-Fehlberg method as soon as we know the
corresponding Odespy name, which is RKFehlberg:
compare(odespy_methods=[odespy.EulerCromer, odespy.RKFehlberg],
omega=2, X_0=2, number_of_periods=200,
time_intervals_per_period=40)
138 4 Solving Ordinary Differential Equations
Fig. 4.23 Comparison of the Runge-Kutta-Fehlberg adaptive method against the Euler-Cromer
scheme for a long time simulation (200 periods)
The 4th-order Runge-Kutta method (RK4) is clearly the most widely used method
to solve ODEs. Its power comes from high accuracy even with not so small time
steps.
4.3 Oscillating One-Dimensional Systems 139
Fig. 4.24 The last 10 of 40 periods of oscillations by the 4th-order Runge-Kutta method
where
unC Ã
fQnC 12 D f Âun C 1
2 tf
tfOnC12;t
n;tnC12
;Ã (4.60)
nC
fQnC12 Á12 ; (4.61)
Application We can run the same simulation as in Figs. 4.16, 4.18, and 4.21, for 40
periods. The 10 last periods are shown in Fig. 4.24. The results look as impressive
as those of the Euler-Cromer method.
We start with integrating the general ODE u' = f(u, t) over a time step, from th
to In-1,
In-H1
The goal of the computation is u(t, 11) (u"+"), while u(t,) (u") is the most recently
known value of u. The challenge with the integral is that the integrand involves the
unknown u between tº and tº 11.
The integral can be approximated by the famous Simpson's rule":
In-1
In
The problem with this formula is that we do not know f"** = f(u"++, ºn tº )
and f"*" = (u", tº 11) as only u" is available and only f" can then readily be
computed.
To proceed, the idea is to use various approximations for f* and f" based
on using well-known schemes for the ODE in the intervals [th, t, } | and [th, tº 11].
Let us split the integral into four terms:
In-El
At
ſ f(u0t), t)dt as #(ºr fºr fºr fº). a - -
In
where fn H, f***, and f* are approximations to f"; and f" that can uti
lize already computed quantities. For f "tº we can simply apply an approximation
1 -
f*} = f (r Arº)
+ (4.63)
This formula provides a prediction of f***, so we can for f*** try a Backward
Euler method to approximate un-H;
fin-i-k
f"+3 = (,n +. 5A
I
f *...) &n+4
- (4.64)
With f" as an approximation to f "**, we can for the final term fºr use
a midpoint method (or central difference, also called a Crank-Nicolson method) to
approximate u"":
f*" = f(u" + At f"**, 1,11). (4.65)
We have now used the Forward and Backward Euler methods as well as the cen
tered difference approximation in the context of Simpson's rule. The hope is that
"http://en.wikipedia.org/wiki/Simpson's rule
4.3 Oscillating One-Dimensional Systems 141
Our model problem u00 C!2u D 0 is the simplest possible mathematical model for
oscillating systems. Nevertheless, this model makes strong demands to numerical
methods, as we have seen, and is very useful as a benchmark for evaluating the
performance of numerical methods.
Real-life applications involve more physical effects, which lead to a differential
equation with more terms and also more complicated terms. Typically, one has
a damping force f.u0/ and a spring force s.u/. Both these forces may depend non
linearly on their argument, u0 or u. In addition, environmental forces F.t/ may act
on the system. For example, the classical pendulum has a nonlinear “spring” or
restoring force s.u/ sin.u/, and air resistance on the pendulum leads to a damp
ing force f.u0/ ju0ju0. Examples on environmental forces include shaking of the
ground (e.g., due to an earthquake) as well as forces from waves and wind.
With three types of forces on the system: F, f, and s, the sum of forces is written
F.t/ f.u0/ s.u/. Note the minus sign in front of f and s, which indicates
that these functions are defined such that they represent forces acting against the
motion. For example, springs attached to the wheels in a car are combined with
effective dampers, each providing a damping force f.u0/ D bu0 that acts against
the spring velocity u0. The corresponding physical force is then f : bu0, which
points downwards when the spring is being stretched (and u0 points upwards), while
f acts upwards when the spring is being compressed (and u0 points downwards).
Figure 4.25 shows an example of a mass m attached to a potentially nonlinear
spring and dashpot, and subject to an environmental force F.t/. Nevertheless, our
general
and
body,f.Pu/
and
model
%Dis 12tcan
CDdensity
he A%
equally
ÂjPÂjPof
well
(where
air).
be aCD
pendulum as in Fig. 4.26 with s.u/ D mg sin Â
D 0:4, A is the cross sectional area of the
Newton’s second law for the system can be written with the mass times acceler
ation on the left-hand side and the forces on the right-hand side:
Because the differential equation is of second order, due to the term u00, we need
two initial conditions:
u.0/ D U0; u0.0/ D V0: (4.67)
Note that with the choices f .u0/ D 0, ps.u/ D ku, and F.t/ D 0 we recover the
original ODE u00 C !2u D 0 with ! D k=m.
How can we solve (4.66)? As for the simple ODE u00 C !2u D 0, we start by
rewriting the second-order ODE as a system of two first-order ODEs:
v0Dm1.F.t/s.u/f.v//; (4.68)
u0 D v: (4.69)
Any method for a system of first-order ODEs can be used to solve for u.t/ and
v.t/.
We can easily solve for the new unknowns vnC1 and unC1:
vnC1 D vn C mt
.F.tn / s.un/ f.vn//; (4.72)
unC1 D un C tvnC1: (4.73)
vnC1 C mt f.vnC1/ D vn C mt
F.tnC1/ s.unC1/ ;
u = zeros(N_t+1)
v = zeros(N_t+1)
# Initial condition
u[0] = U_0
v[0] = V_0
144 4 Solving Ordinary Differential Equations
The 4-th order Runge-Kutta method The RK4 method just evaluates the right
hand side of the ODE system,
.m1.F.t/s.u/f.v//;v/
for known values of u, v, and t, so the method is very simple to use regardless of
how the functions s.u/ and f.v/ are chosen.
We consider an engineering system with a linear spring, s.u/ D kx, and a viscous
damper, where the damping force is proportional to u0, f.u0/ D bu0, for some
constant b > 0. This choice may model the vertical spring system in a car (but
engineers often like to illustrate such a system by a horizontal moving mass like
the one depicted in Fig. 4.25). We may choose simple values for the constants to
illustrate basic effects of damping (and later excitations). Choosing the oscillations
to be the simple u.t/ D cost function in the undamped case, we may set m D 1,
k D 1, b D 0:3, U0 D 1, V0 D 0. The following function implements this case:
def linear_damping():
b = 0.3
f = lambda v: b*v
s = lambda u: k*u
F = lambda t: 0
m = 1
k = 1
U_0 = 1
V_0 = 0
T = 12*pi
dt = T/5000.
The plot_u function is a collection of plot statements for plotting u.t/, or a part
of it. Figure 4.27 shows the effect of the bu0 term: we have oscillations with (an
approximate) period 2, as expected, but the amplitude is efficiently damped.
t u
Nt D ; Nu D ;
tc uc
wherethatand
such uc Nuare
tc Nt and have
characteristic
their sizes of time and displacement, respectively,
we can choose uc typicalpsize
m=k.around
This unity. In the
gives the presentscaled
following problem,
(or
D U0 and tc D
dimensionless) problem for the dimensionless quantity Nu.Nt/:
Cˇ
d2Nu
dNt2 ddNu b
NtC Nu D0; Nu.0/D1; Nu0.0/D0; ˇD p :
mk
The striking fact is that there is only one physical parameter in this problem:
the dimensionless number ˇ. Solving this problem corresponds to solving the
original problem (with dimensions) with the parameters m D k D U0 D 1 and
b D ˇ. However, solving the dimensionless problem is more general: if we have
a solution Nu.NtIˇ/, we can find the physical solution of a range of problems since
u.t/ D U0 pk=mIˇÁ
Nu t :
As long as ˇ is fixed, we can find u for any U0, k, and m from the above for
mula! In this way, a time consuming simulation can be done only once, but still
provide many solutions. This demonstrates the power of working with scaled or
dimensionless problems.
146 4 Solving Ordinary Differential Equations
We now extend the previous example to also involve some external oscillating force
on the system: F.t/ D Asin.wt/. Driving a car on a road with sinusoidal bumps
might give such an external excitation on the spring system in the car (w is related
to the velocity of the car).
With A D 0:5 and w D 3,
we get the graph in Fig. 4.28. The striking difference from Fig. 4.27 is that the
oscillations start out as a damped cost signal without much influence of the external
force, but then the free oscillations of the undamped system (cost) u00 C u D 0
die out and the external force 0:5sin.3t/ induces oscillations with a shorter period
2=3. You are encouraged to play around with a larger A and switch from a sine to
a cosine in F and observe the effects. If you look this up in a physics book, you can
find exact analytical solutions to the differential equation problem in these cases.
A particularly interesting case arises when the excitation force has the same fre
quency as the free oscillations of the undamped system, i.e., F.t/ D Asint. With
the same amplitude A D 0:5, but a smaller damping b D 0:1, the oscillations in
Fig. 4.28 becomes qualitatively very different as the amplitude grows significantly
larger over some periods. This phenomenon is called resonance and is exemplified
in Fig. 4.29. Removing the damping results in an amplitude that grows linearly in
time.
Fig. 4.28 Effect of linear damping in combination with a sinusoidal external force
4.3 Oscillating One-Dimensional Systems 147
A body with mass m is attached to a spring with stiffness k while sliding on a plane
surface. The body is also subject to a friction force f.u0/ due to the contact between
the body and the plane. Figure 4.30 depicts the situation. The friction force f.u0/
can be modeled by Coulomb friction:
ˆ
< mg; u0 < 0;
f.u0/ D 8 mg; u0 > 0;
:̂
0; u0 D 0
where is the friction coefficient, and mg is the normal force on the surface where
the body slides. This formula can also be written as f.u0/ D mg sign.u0/, pro
Fig. 4.30 Sketch of a one-dimensional, oscillating dynamic system subject to sliding friction and
a spring force
148 4 Solving Ordinary Differential Equations
this
videdproperty).
the signum
To function
check that
sign.x/
the signs
is defined
in the definition
to be zeroof
forfxare
D0 right,
(numpy.signhas
recall that the
actual physical force is f and this is positive (i.e., f <0) when it works against
the body moving with velocity u0 < 0.
The nonlinear spring force is taken as
s.u/ D k˛ 1tanh.˛u/;
which is approximately ku for small u, but stabilizes at ˙k=˛ for large ˙˛u.
Here is a plot with k D 1000 and u 2 Œ0:1; 0:1 for three ˛ values:
If there is no external excitation force acting on the body, we have the equation
of motion
mu00 C mg sign.u0/ C k˛1 tanh.˛u/ D 0:
Let us simulate a situation where a body of mass 1 kg slides on a surface with
D 0:4, while attached to a spring with stiffness k D 1000kg=s2. The initial
displacement of the body is 10 cm, and the ˛ parameter in s.u/ is set to 60 1/m.
Using the EulerCromer function from the osc_EC_general code, we can write
a function sliding_frictionfor solving this problem:
def sliding_friction():
from numpy import tanh, sign
f = lambda
alpha = 60.0
v: mu*m*g*sign(v)
s = lambda t:
F u: 0
k/alpha*tanh(alpha*u)
g = 9.81
mu = 0.4
m = 1
k = 1000
4.3 Oscillating One-Dimensional Systems 149
Fig. 4.31 Effect of nonlinear (left) and linear (right) spring on sliding friction
U_0 = 0.1
V_0 = 0
T = 2
dt = T/5000.
Running the sliding_friction function gives us the results in Fig. 4.31 with
s.u/ D k˛1 tanh.˛u/ (left) and the linearized version s.u/ D ku (right).
without rewriting the ODE as a system of first-order ODEs. The primary motivation
for “yet another solution method” is that the discretization principles result in a very
good scheme, and more importantly, the thinking around the discretization can be
reused when solving partial differential equations.
The main idea of this numerical method is to approximate the second-order
derivative u00 by a finite difference. While there are several choices of difference
approximations to first-order derivatives, there is one dominating formula for the
second-order derivative:
unC1 2un C un 1
u00.tn/ t2 : (4.74)
The error in this approximation is proportional to t2. Letting the ODE be valid at
some arbitrary time point tn,
u00.tn/ C !2u.tn/ D 0;
150 4 Solving Ordinary Differential Equations
We now assume that un1 and un are already computed and that unC1 is the new
unknown. Solving with respect to unC1 gives
A major problem arises when we want to start the scheme. We know that u0 D
U0, but applying (4.76) for n D 0 to compute u1 leads to
where we do not know u1. The initial condition u0.0/ D 0 can help us to eliminate
u1 - and this condition must anyway be incorporated in some way. To this end, we
discretize u0.0/ D 0 by a centered difference,
1
u0.0/ u12tu
D 0:
It follows that u1 D u1, and we can use this relation to eliminate u1 in (4.77):
1
u1 D u0 2t2!2u0 : (4.78)
With u0 D U0 and u1 computed from (4.78), we can compute u2, u3, and so forth
from (4.76). Exercise 4.19 asks you to explore how the steps above are modified in
case we have a nonzero initial condition u0.0/ D V0.
u0.0/ u1 tu0
D 0;
leading to u1 D u0. Then we can use (4.76) for the coming time steps. How
ever, this forward difference has an error proportional to t, while the centered
difference we used has an error proportional to t2, which is compatible with
the accuracy (error goes like t2) used in the discretization of the differential
equation.
The method for the second-order ODE described above goes under the name
Störmer’s method or Verlet integration7. It turns out that this method is mathemat
ically equivalent with the Euler-Cromer scheme (!). Or more precisely, the general
formula (4.76) is equivalent with the Euler-Cromer formula, but the scheme for the
7http://en.wikipedia.org/wiki/Verlet_integration
4.3 Oscillating One-Dimensional Systems 151
first time level (4.78) implements the initial condition u'(0) slightly more accurately
than what is naturally done in the Euler-Cromer scheme. The latter will do
dt = float (dt)
Nt = int (round (T/dt))
u = zeros (Nt--1)
t = linspace (0, Ntºdt, Nt-1)
uſ)] = U_0
uſ1] = u [0] – 0.5+dtº:2+omega++2+u [0]
for n in range (1, Nt):
uſn+1] = 2+u [n] — uſn-1] – dtº #2+omega++2+u [n]
return u, t
A key issue is how to generalize the scheme from Sect. 4.3.12 to a differential
equation with more terms. We start with the case of a linear damping term f(u') =
bu', a possibly nonlinear spring force s(u), and an excitation force F(t):
mu" + bu' + s(u) = F(t), u(0) = Uo, u'(0) = 0, t e (0, T). (4.79)
We need to find the appropriate difference approximation to u' in the bu’ term.
A good choice is the centered difference
un-Fl - un-l
u'(th) 2S, —H- - (4.80)
where Fn is a short notation for F.tn/. Equation (4.81) is linear in the unknown
unC1, so we can easily solve for this quantity:
we get
unC1 2un
t2 C un1 Â unC1 un1 Ã
m Cf C s.un/ D Fn;
2t
which is a nonlinear algebraic equation for unC1 that must be solved by numerical
methods. A much more convenient scheme arises from using a backward difference
for u0,
un un 1
u0.tn/ ;
t
because the damping term will then be known, involving only un and un1, and we
can easily solve for unC1.
The downside of the backward difference compared to the centered difference
(4.80) is that it reduces the order of the accuracy in the overall scheme from t2
to t. In fact, the Euler-Cromer scheme evaluates a nonlinear damping term as
f.vn/when computingvnC1, and this is equivalent to using the backward difference
above. Consequently, the convenience of the Euler-Cromer scheme for nonlinear
damping comes at a cost of lowering the overall accuracy of the scheme from sec
ond to first order in t. Using the same trick in the finite difference scheme for
the second-order differential equation, i.e., using the backward difference in f.u0/,
makes this scheme equally convenient and accurate as the Euler-Cromer scheme in
the general nonlinear case mu00 C f.u0/ C s.u/ D F.
4.4 Exercises 153
4.4 Exercises
a) Start at t D 0 and draw a straight line with slope u0.0/ D u.0/ D 1. Go one
time step forward to t D t and mark the solution point on the line.
b) Draw a straight line through the solution point .t; u1/ with slope u0.t/ D u1.
Go one time step forward to t D 2t and mark the solution point on the line.
c) Draw a straight line through the solution point .2t; u2/ with slope u0.2t/ D
u2. Go one time step forward to t D 3t and mark the solution point on the
line.
d) Set up the Forward Euler scheme for the problem u0 D u. Calculate u1, u2, and
u3. Check that the numbers are the same as obtained in a)-c).
Filename: ForwardEuler_geometric_solution.py.
Exercise 4.2: Make test functions for the Forward Euler method
The purpose of this exercise is to make a file test_ode_FE.py that makes use of
the ode_FE function in the file ode_FE.py and automatically verifies the imple
mentation of ode_FE.
Filename: test_ode_FE.py.
c) For the case in b), find through experimentation the largest value of t where
the exact solution and the numerical solution by Heun’s method cannot be dis
tinguished visually. It is of interest to see how far off the curve the Forward
Euler method is when Heun’s method can be regarded as “exact” (for visual
purposes).
Filename: ode_Heun.py.
Hint Extend the logistic.py file. Introduce a loop over k, write out tk, and
ask the user if the loop is to be continued.
Filename: logistic_dt.py.
Hint Import the ode_FE function from the ode_system_FE module and make
a modified demo_SIR function that has a loop over repeatedly halved time steps.
Plot S, I, and R versus time for the two last time step sizes in the same plot.
Filename: SIR_dt.py.
Write up the complete model, implement it, and rerun the case from Sect. 4.2.8
with various choices of parameters to illustrate various effects.
Filename: SIRV1_V2S.py.
a) Find an expression for the Nn in terms of Nn1 and formulate an algorithm for
computing Nn, n D 1;2;:::;Nt.
b) Implement the algorithm in a) in a function growth_BE(N_0, dt, T) for solv
ing N0 D rN, N.0/ D N0, t 2 .0; T, with time step t (dt).
c) Implement the Forward Euler scheme in a function growth_FE(N_0, dt, T)
as described in b).
d) Compare visually the solution produced by the Forward and Backward Euler
schemes with the exact solution when r D 1 and T D 6. Make two plots, one
with t D 0:5 and one with t D 0:05.
Filename: growth_BE.py.
This type of difference, applied at the point tnC 12 D tn C 12t, is illustrated geomet
rically in Fig. 4.20.
a) Insert the finite difference approximation in the ODE N0 D rN and solve for
the unknown NnC1, assuming Nn is already computed and hence known. The
resulting computational scheme is often referred to as a Crank-Nicolson scheme.
b) Implement the algorithm in a) in a function growth_CN(N_0, dt, T) for solv
ing N0 D rN, N.0/ D N0, t 2 .0; T, with time step t (dt).
4.4 Exercises 157
c) Make plots for comparing the Crank-Nicolson scheme with the Forward and
Backward Euler schemes in the same test problem as in Exercise 4.11.
Filename: growth_CN.py.
d a/C 1d22Šdx2f.a/.xa/2
f.x/ D C
f.a/C dx f.a/.x
1d33Šdx3f.a/.xa/3 C :::
iD0
1X 1 di
iŠdxif.a/.xa/i :
D
For a function of time, as addressed in our ODE problems, we would use u instead
of f, t instead of x, and a time point tn instead of a:
u.t/Du.tn ddt1d22Šdt2/2
/ Cu.tn/.ttn/Cu.tn/.ttn
1 d3
C 3Šdt3 /3 C :::
u.tn/.t tn
1X
iD0 1 di
iŠdtiu.tn/.ttn/i:
D
We can justify this formula mathematically through Taylor series. Write up the
the expression
Taylor series for
with
u.tn C t/
respect to(around
u0.tn/. Identify,
t D tn, as given above), and then solve
on the right-hand side, the finite
difference approximation and an infinite series. This series is then the error in
the finite difference approximation. If t is assumed small (i.e. t << 1), t
will be much larger than t2, which will be much larger than t3, and so on.
The leading order term in the series for the error, i.e., the error with the least
power of t is a good approximation of the error. Identify this term.
b) Repeat a) for a backward difference:
u.tn/ u.tn t/
u0.tn/ t :
This
to u0.tn/,
time, write up the Taylor series for u.tn t/ around tn. Solve with respect
and identify the leading order term in the error. How is the error
compared to the forward difference?
158 4 Solving Ordinary Differential Equations
2
1 u.tn C t/t u.tn/
u0.tn C t/ :
Write up the Taylor series for u.tn/ around tn C 12t and the Taylor series for
the
u0.t C 12 t/t/,around
u.tnnright-hand identify C finite
tnthe 12t. Subtract the two series, solve with respect to
difference approximation and the error terms on
side, and write up the leading order error term. How is this term
compared to the ones for the forward and backward differences?
d) Can you use the leading order error terms in a)-c) to explain the visual observa
tions in the numerical experiment in Exercise 4.12?
e) Find the leading order error term in the following standard finite difference ap
proximation to the second-order derivative:
Hint Express u.tn˙ t/ via Taylor series and insert them in the difference formula.
Filename: Taylor_differences.pdf.
un tf.un;tn/ D un1:
Filename: osc_BE.py.
Remarks While the Forward Euler method applied to oscillation problems u00 C
!2u D 0 gives growing amplitudes, the Backward Euler method leads to signifi
cantly damped amplitudes.
Filename: osc_BE.py.
Exercise 4.18: Set up a Forward Euler scheme for nonlinear and damped
oscillations
Derive a Forward Euler method for the ODE system (4.68)–(4.69). Compare
the method with the Euler-Cromer scheme for the sliding friction problem from
Sect. 4.3.11:
Filename: osc_FE_general.py.
Open Access This chapter is distributed under the terms of the Creative Commons Attribution
NonCommercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/),
which permits any noncommercial use, duplication, adaptation, distribution and reproduction
in any medium or format, as long as you give appropriate credit to the original author(s) and the
source, a link is provided to the Creative Commons license and any changes made are indicated.
The images or other third party material in this chapter are included in the work’s Creative
Commons license, unless indicated otherwise in the credit line; if such material is not included
in the work’s Creative Commons license and the respective action is not permitted by statutory
regulation, users will need to obtain permission from the license holder to duplicate, adapt or
reproduce the material.
Solving Partial Differential Equations
5
The subject of partial differential equations (PDEs) is enormous. At the same time,
it is very important, since so many phenomena in nature and technology find their
mathematical formulation through such equations. Knowing how to solve at least
some PDEs is therefore of great importance to engineers. In an introductory book
like this, nowhere near full justice to the subject can be made. However, we still
find it valuable to give the reader a glimpse of the topic by presenting a few basic
and general methods that we will apply to a very common type of PDE.
We shall focus on one of the most widely encountered partial differential equa
tions: the diffusion equation, which in one dimension looks like
@u ˇ@x2@2u C
@t D g:
We should also mention that the diffusion equation may appear after simplifying
more complicated partial differential equations. For example, flow of a viscous fluid
between two flat and parallel plates is described by a one-dimensional diffusion
equation, where u then is the fluid velocity.
A partial differential equation is solved in some domain ˝ in space and for
a time interval Œ0; T. The solution of the equation is not unique unless we also
prescribe initial and boundary conditions. The type and number of such conditions
depend on the type of equation. For the diffusion equation, we need one initial con
dition, u.x; 0/, stating what u is when the process starts. In addition, the diffusion
equation needs one boundary condition at each point of the boundary @˝ of ˝.
This condition can either be that u is known or that we know the normal derivative,
ru n D @u=@n (n denotes an outward unit normal to @˝).
Let us look at a specific application and how the diffusion equation with initial
and boundary conditions then appears. We consider the evolution of temperature in
a one-dimensional medium, more precisely a long rod, where the surface of the rod
is covered by an insulating material. The heat can then not escape from the surface,
which means that the temperature distribution will only depend on a coordinate
along the rod, x, and time t. At one end of the rod, x D L, we also assume that the
surface is insulated, but at the other end, x D 0, we assume that we have some de
vice for controlling the temperature of the medium. Here, a function s.t/ tells what
the temperature is in time. We therefore have a boundary condition u.0;t/ D s.t/.
At the other insulated end, x D L, heat cannot escape, which is expressed by the
boundary condition @u.L;t/=@x D 0. The surface along the rod is also insulated
and hence subject to the same boundary condition (here generalized to @u=@n D 0
at the curved surface). However, since we have reduced the problem to one dimen
sion, we do not need this physical boundary condition in our mathematical model.
In one dimension, we can set ˝ D Œ0; L.
To summarize, the partial differential equation with initial and boundary condi
tions reads
@u.x;t/@t D ˇ@2u.x;t/@x2C g.x;t/; x2 .0;L/ ;t 2 .0;T; (5.1)
Mathematically, we assume that at t D 0, the initial condition (5.4) holds and that
the partial differential equation (5.1) comes into play for t>0. Similarly, at the end
points, the boundary conditions (5.2) and (5.3) govern u and the equation therefore
is valid for x 2 .0; L/.
What about the source term g in our example with temperature distribution in
a rod? g.x;t/ models heat generation inside the rod. One could think of chemical
reactions at a microscopic level in some materials as a reason to include g. How
ever, in most applications with temperature evolution, g is zero and heat generation
usually takes place at the boundary (as in our example with u.0;t/ D s.t/).
Before continuing, we may consider an example of how the temperature distri
bution evolves in the rod. At time t D 0, we assume that the temperature is 10ıC.
Then we suddenly apply a device at x D 0 that keeps the temperature at 50ı C at
this end. What happens inside the rod? Intuitively, you think that the heat genera
tion at the end will warm up the material in the vicinity of x D 0, and as time goes
by, more and more of the rod will be heated, before the entire rod has a temperature
of 50ı C (recall that no heat escapes from the surface of the rod).
Mathematically, (with the temperature in Kelvin) this example has I.x/ D 283
K, except at the end point: I.0/ D 323 K, s.t/ D 323 K, and g D 0. The figure
below shows snapshots from four different times in the evolution of the temperature.
We shall now construct a numerical method for the diffusion equation. We know
how to solve ordinary differential equations, so in a way we are able to deal with
the time derivative. Very often in mathematics, a new problem can be solved by
reducing it to a series of problems we know how to solve. In the present case,
it means that we must do something with the spatial derivative @2=@x2 in order
164 5 Solving Partial Differential Equations
The space between two mesh points xi and xiC1, i.e. the interval Œxi;xiC1, is call
a cell. We shall here, for simplicity, assume that each cell has the same length
x D xiC1 xi, i D 0;:::;N 1.
The partial differential equation is valid at all spatial points x 2 ˝, but we may
relax this condition and demand that it is fulfilled at the internal mesh points only,
x1;:::;xN1:
@t ;t/
@u.xi @2u.xi ;t/ 1:
Dˇ C g.xi;t/; i D 1;:::;N (5.5)
@x2
Now, at any point xi we can approximate the second-order derivative by a finite
difference:
@2u.xi ;t/ u.xiC1;t/ 2u.xi ;t/ C u.xi1;t/
: (5.6)
@x2 x2
It is common to introduce a short notation ui.t/ for u.xi;t/, i.e., u approximated at
some mesh point xi in space. With this new notation we can, after inserting (5.6)
in (5.5), write an approximation to the partial differential equation at mesh point
.xi ;t) as
dui .t/ uiC1.t/ x2 C ui1.t/
2ui.t/
dt D ˇ C gi .t/; i D 1;:::;N 1: (5.7)
Note that we have adopted the notation gi.t/ for g.xi ;t/ too.
What is (5.7)? This is nothing but a system of ordinary differential equations in
N 1 unknowns u1.t/; :::; uN1.t/! In other words, with aid of the finite differ
ence approximation (5.6), we have reduced the single partial differential equation
to a system of ODEs, which we know how to solve. In the literature, this strategy is
called the method of lines.
We need to look into the initial and boundary conditions as well. The initial con
dition u.x; 0/ D I.x/ translates to an initial condition for every unknown function
ui.t/: ui.0/ D I.xi/, i D 0;:::;N. At the boundary x D 0 we need an ODE in
our ODE system, which must come from the boundary condition at this point. The
boundary condition reads u.0;t/ D s.t/. We can derive an ODE from this equation
particular
we
for
by differentiating
u0
use
0 since
theequation
equation
that equation
both
weu00salso
.t/involves
ides:
D
need
u0s0.t/
0.t/
toD s0.t/. The ODE system above cannot be used
some
derived
quantity
from the
u0 1boundary
outside the
condition.
domain. Instead,
For this
make sure the initial condition is u0.0/ D s.0/
(otherwise nothing will happen: we get u D 283 K forever).
5.1 Finite Difference Methods 165
We remark that a separate ODE for the (known) boundary condition u0 D s.t/
is not strictly needed. We can just work with the ODE system for u1;:::;uN, and
in the ODE for u0, replace u0.t/ by s.t/. However, these authors prefer to have an
ODE for every point value ui, i D 0;:::;N, which requires formulating the known
boundary at x D 0 as an ODE. The reason for including the boundary values in the
ODE system is that the solution of the system is then the complete solution at all
mesh points, which is convenient, since special treatment of the boundary values is
then avoided.
The condition @u=@x D 0 at x D L is a bit more complicated, but we can
approximate the spatial derivative by a centered finite difference:
@x ˇ
@u ˇˇ uNC1 uN 1
ˇ D 0:
iDN 2 x
x, whileODE
This
sponding
gives the
a simple
centered
u0N Dequation
u0aNpproximation
uN D uN1 for the boundary value, and a corre
1. However, this approximation has an error of order
we used above has an error of order x2.
The finite difference approximation we used for the second-order derivative in
the diffusion equation also has an error of order x2. Thus, if we use the sim
pler one-sided difference above, it turns out that we reduce the overall accuracy
of the method.
dui ˇ
dt D x2.uiC1.t/ 2ui.t/ C ui 1.t// C gi.t/; i D 1;:::;N 1; (5.10)
duNdt D2ˇx2.uN 1
.t/uN.t// CgN.t/: (5.11)
166 5 Solving Partial Differential Equations
At this point, it is tempting to implement a real physical case and run it. However,
partial differential equations constitute a non-trivial topic where mathematical and
programming mistakes come easy. A better start is therefore to address a carefully
designed test example where we can check that the method works. The most attrac
tive examples for testing implementations are those without approximation errors,
because we know exactly what numbers the program should produce. It turns out
that solutions u.x;t/ that are linear in time and in space can be exactly reproduced
by most numerical methods for partial differential equations. A candidate solution
might be
u.x;t/ D .3t C 2/.x L/:
Inserting this u in the governing equation gives
uNC1.t/ uN 1.t/
2 x D ) uNC1 D uN1 C 2x;
This is a matter of translating (5.9), (5.10), and (5.14) to Python code (in file
test diffusion_pde_exact linear. py):
def s(t):
return u_exact (0, t)
Note that dudz (t) is the function representing the y parameter in (5.14). Also note
that the rhs function relies on access to global variables beta, dx, L, and x, and
global functions dsdt, g, and dudz.
We expect the solution to be correct regardless of N and At, so we can choose
a small N, N = 4, and At = 0.1. A test function with N = 4 goes like
tol = 1E-12
for i in range(0, u.shape[0]):
diff
assert
= abs(u_exact(x,
diff < tol, ’diff=%.16g’
t[i]) - u[i,:]).max()
% diff
With N D 4 we reproduce the linear solution exactly. This brings confidence to the
implementation, which is just what we need for attacking a real physical problem
next.
Let us return to the case with heat conduction in a rod (5.1)–(5.4). Assume that
the rod is 50 cm long and made of aluminum alloy 6082. The ˇ parameter equals
Ä=.%c/, where Ä is the heat conduction coefficient, % is the density, and c is the
aluminum
heat capacity.
alloy
We6082:
can find 2:7 values
% Dproper 103 kg/m3,
for these
Ä Dphysical
200 mKWquantities
, c D 900 in Kkg
the
J case of
. This
results in ˇ D Ä=.%c/ D 8:2 105 m2=s. Preliminary simulations show that we are
close to a constant steady state temperature after 1 h, i.e., T D 3600 s.
The rhs function from the previous section can be reused, only the functions s,
dsdt, g, and dudx must be changed (see file rod_FE.py):
def dudx(t):
return 0
def s(t):
return 323
5.1 Finite Difference Methods 169
L = 0.5
beta = 8. 2E-5
N = 40
x = linspace (0, L, N41)
dx = x [1] – x [O]
u = zeros (N+1)
Let us use At = 0.00034375. We can now call ode_FE and then make an animation
on the screen to see how u(x,t) develops in time:
# Make movie
import os
os. system (’rm timp + .png’)
import matplotlib. pyplot as plt
plt. ion ()
y = u [0, :]
lines = plt. plot (x, y)
plt. axis ([x [0], x [-1], 273, s(0)+10])
plt. xlabel ('x')
plt. ylabel ('u(x,t) . )
counter = 0
# Plot each of the first 100 frames, then increase speed by 10x
change_speed = 100
for i in range (0, u. shape [0]):
print t [i]
plot = True if i <= change_speed else i / 10 ==
lines [0]. set_ydata (uſi, : ])
if i > change_speed:
plt. legend ([?t=%. Of 10x’ A t [i]])
else:
The plotting statements update the u.x;t/ curve on the screen. In addi
tion, we save a fraction of the plots to files tmp_0000.png, tmp_0001.png,
tmp_0002.png, and so on. These plots can be combined to ordinary video files.
A common tool is ffmpeg or its sister avconv.
These programs take the same type of command-line options. To make a Flash
video movie.flv, run
Terminal
The -i option specifies the naming of the plot files in printf syntax, and -r specifies
the number of frames per second in the movie. On Mac, run ffmpeg instead of
avconv with the same options. Other video formats, such as MP4, WebM, and Ogg
can also be produced:
Terminal
The results of a simulation start out as in Figs. 5.1 and 5.2. We see that the solu
tion definitely looks wrong. The temperature is expected to be smooth, not having
such a saw-tooth shape. Also, after some time (Fig. 5.2), the temperature starts to
increase much more than expected. We say that this solution is unstable, meaning
that it does not display the same characteristics as the true, physical solution. Even
though we tested the code carefully in the previous section, it does not seem to work
for a physical application! How can that be?
The problem is that t is too large, making the solution unstable. It turns out
that the Forward Euler time integration method puts a restriction on the size of t.
For the heat equation and the way we have discretized it, this restriction can be
shown to be [15]
t Ä x2
2ˇ : (5.15)
This is called a stability criterion. With the chosen parameters, (5.15) tells us that
the upper limit is t D 0:0003125, which is smaller than our choice above. Re
running the case with a t equal to x2=.2ˇ/, indeed shows a smooth evolution of
u.x;t/. Find the program rod_FE.py and run it to see an animation of the u.x;t/
function on the screen.
@Nu
@Nt D @2Nu
; Nx 2 .0; 1/:
@Nx2
Note that in this equation, there are no physical parameters! In other words, we
have found a model that is independent of the length of the rod and the material
it is made of (!).
We can easily solve this equation with our program by setting ˇ D 1, L D 1,
I.x/ D 0, and s.t/ D 1. It turns out that the total simulation time (to “infin
ity”) can be taken as 1.2. When we have the solution Nu.Nx; Nt/, the solution with
dimension Kelvin, reflecting the true temperature in our medium, is given by
Through this formula we can quickly generate the solutions for a rod made of
aluminum,
Figure 5.3
wood,
showsor four
rubber - it is just
snapshots ofathe
matter
scaled
of(dimensionless)
plugging in the solution
right ˇ value.
.Nx;N Nt/.
The power of scaling is to reduce the number of physical parameters in a prob
lem, and in the present case, we found one single problem that is independent of
the material (ˇ) and the geometry (L).
5.1.5 Vectorization
Occasionally in this book, we show how to speed up code by replacing loops over
arrays by vectorized expressions. The present problem involves a loop for comput
ing the right-hand side:
This loop can be replaced by a vectorized expression with the following reasoning.
We want to set all the inner points at once: rhs[1:N-1] (this goes from index 1
up to, but not including, N). As the loop index i runs from 1 to N-1, the u[i+1]
term will cover all the inner u values displaced one index to the right (compared
to 1:N-1), i.e., u[2:N]. Similarly, u[i-1] corresponds to all inner u values dis
placed one index to the left: u[0:N-2]. Finally, u[i] has the same indices as rhs:
u[1:N-1]. The vectorized loop can therefore be written in terms of slices:
5.1 Finite Difference Methods 173
This rewrite speeds up the code by about a factor of 10. A complete code is found
in the file rod_FE_vec.py.
Let us now show how to apply a general ODE package like Odespy (see Sect. 4.3.6)
to solve our diffusion problem. As long as we have defined a right-hand side func
tion rhs this is very straightforward:
import odespy
solver
T
solver.set_initial_condition(U_0)
= 1.2= odespy.RKFehlberg(rhs)
N_t = int(round(T/float(dt)))
time_points = linspace(0, T, N_t+1)
u, t = solver.solve(time_points)
174 5 Solving Partial Differential Equations
Fig. 5.4 Time steps used by the Runge-Kutta-Fehlberg method: error tolerance 103 (left) and
106 (right)
The very nice thing is that we can now easily experiment with many different
integration methods. Trying out some simple ones first, like RK2 and RK4, quickly
reveals that the time step limitation of the Forward Euler scheme also applies to
these more sophisticated Runge-Kutta methods, but their accuracy is better. How
ever, the Odespy package offers also adaptive methods. We can then specify a much
larger time step in time_points, and the solver will figure out the appropriate
step. Above we indicated how to use the adaptive Runge-Kutta-Fehlberg 4–5 solver.
While the t corresponding to the Forward Euler method requires over 8000 steps
for a simulation, we started the RKFehlberg method with 100 times this time step
and in the end it required just slightly more than 2500 steps, using the default tol
erance parameters. Lowering the tolerance did not save any significant amount of
computational work. Figure 5.4 shows a comparison of the length of all the time
steps for two values of the tolerance. We see that the influence of the tolerance is mi
nor in this computational example, so it seems that the blow-up due to instability is
what governs the time step size. The nice feature of this adaptive method is that we
can just specify when we want the solution to be computed, and the method figures
out on its own what time step that has to be used because of stability restrictions.
We have seen how easy it is to apply sophisticated methods for ODEs to this
PDE example. We shall take the use of Odespy one step further in the next section.
A major problem with the stability criterion (5.15) is that the time step becomes
very small if x is small. For example, halving x requires four times as many
time steps and eight times the work. Now, with N D 40, which is a reasonable
resolution for the test problem above, the computations are very fast. What takes
5.1 Finite Difference Methods 175
time, is the visualization on the screen, but for that purpose one can visualize only
a subset of the time steps. However, there are occasions when you need to take
larger time steps with the diffusion equation, especially if interest is in the long
term behavior as t ! 1. You must then turn to implicit methods for ODEs. These
methods require the solutions of linear systems, if the underlying PDE is linear, and
systems of nonlinear algebraic equations if the underlying PDE is non-linear.
The simplest implicit method is the Backward Euler scheme, which puts no re
strictions on t for stability, but obviously, a large t leads to inaccurate results.
The Backward Euler scheme for a scalar ODE u0 D f.u;t/ reads
unC1 un
t D f.unC1;tnC1/:
unC1
0 t un0 D s0.tnC1/; (5.16)
is easy to realize by writing out the equations for the case N D 3, collecting all
the unknown terms on the left-hand side and all the known terms on the right-hand
side:
unC10 D un0 C t s0.tnC1/; (5.19)
unC11 t x̌2.unC12 2unC11 C unC10/ D un1
C t g1.tnC1/; (5.20)
unC12 t2ˇx2 .unC11 unC12/ D un2 C t g2.tnC1/: (5.21)
A 1.1 = 1 (5.22)
If we want to apply general methods for systems of ODEs on the form u' =
f(u, t), we can assume a linear f(u, t) = Ku. The coefficient matrix K is found
from the right-hand side of (5.16)–(5.18) to be
K1.1 = 0 (5.28)
Kii–1 +. 2, , N
- i - - - - - 1 (5.29)
Kii ==#.
2 -
= 2, ..., N – 1
l (5.31)
28
KNN_1 = ZWyż (5.32)
26
KN,N = TAZ (5.33)
We see that A = I — At K.
To implement the Backward Euler scheme, we can either fill a matrix and call
a linear solver, or we can apply Odespy. We follow the latter strategy. Implicit
methods in Odespy need the K matrix above, given as an argument jac (Jacobian
of f) in the call to odespy. BackwardEuler. Here is the Python code for the
right-hand side of the ODE system (rhs) and the K matrix (K) as well as state
ments for initializing and running the Odespy solver BackwardEuler (in the file
rod_BE. py):
import odespy
solver = odespy.BackwardEuler(rhs, f_is_linear=True, jac=K)
solver = odespy.ThetaRule(rhs, f_is_linear=True, jac=K, theta=0.5)
solver.set_initial_condition(U_0)
T = 1*60*60
N_t = int(round(T/float(dt)))
time_points = linspace(0, T, N_t+1)
u, t = solver.solve(time_points)
The file rod_BE.py has all the details and shows a movie of the solution. We can
run it with any t we want, its size just impacts the accuracy of the first steps.
5.2 Exercises
where P is the period, taken here as 24 hours (24 6060s). The ˇ coefficient may
be set to 106 m2=s. Time is then measured in seconds. Set appropriate values for
T0 and Ta.
a) Show that the present problem has an analytical solution of the form
rx
u.x;t/ D A C Be sin.!t rx/;
Filename: ground_temp.py.
and adaptive
Here, Uin is the
solvers:
exact solution. Use the Odespy package to run the following implicit
1. BackwardEuler
2. Backward2Step
3. RKFehlberg
Experiment to see if you can use larger time steps than what is required by the
Forward Euler method and get solutions with the same order of accuracy.
Hint To avoid oscillations in the solutions when using the RKFehlberg method, the
rtol and atol parameters to RKFFehlberg must be set no larger than 0.001 and
0.0001, respectively. You can print out solver_RKF.t_allto see all the time steps
used by the RKFehlberg solver (if solveris the RKFehlbergobject). You can then
compare the number of time steps with what is required by the other methods.
Filename: ground_temp_adaptive.py.
a) The Crank-Nicolson method for ODEs is very popular when combined with
diffusion equations. For a linear ODE u0 D au it reads
unC1 un
D1
2.aun C aunC1/:
t
Apply the Crank-Nicolson method in time to the ODE system for a one
dimensional diffusion equation. Identify the linear system to be solved.
180 5 Solving Partial Differential Equations
b) The Backward Euler, Forward Euler, and Crank-Nicolson methods can be given
a unified implementation. For a linear ODE u0 D au this formulation is known
as the  rule:
unC1 t un
D .1 Â/aun C ÂaunC1:
d) Consider the physical application from Sect. 5.1.4. Run this case with the  rule
and  D 1=2 for the following values of t: 0.001, 0.01, 0.05. Report what you
see.
Filename: rod_ThetaRule.py.
Remarks Despite the fact that the Crank-Nicolson method, or the  rule with  D
1=2, is theoretically more accurate than the Backward Euler and Forward Euler
schemes, it may exhibit non-physical oscillations as in the present example if the
solution is very steep. The oscillations are damped in time, and decreases with de
creasing t. To avoid oscillations one must have t at maximum twice the stability
limit of the Forward Euler method. This is one reason why the Backward Euler
method (or a 2-step backward scheme, see Exercise 5.3) are popular for diffusion
equations with abrupt initial conditions.
@u
@t D ˇ @2u
@x2; x 2 . 1;1/; t 2 .0; T (5.34)
u.x; 0/ D Â 22 Ã
x2
@@x u.1;t/ D 0p21 exp ; xt22Œ1;
.0; T;
1; (5.36)
(5.35)
The initial condition is the famous and widely used Gaussian function with standard
deviation (or “width”) , which is here taken to be small, D 0:01, such that the
initial condition is a peak. This peak will then diffuse and become lower and wider.
Compute u.x;t/ until u becomes approximately constant over the domain.
Filename: gaussian_diffusion.py.
5.2 Exercises 181
1Z @u@tdx D ˇ 1Z
@d 2u
dx:
@x2
1 1
Using the Gauss divergence theorem on the integral on the right-hand and moving
the time-derivative outside the integral on the left-hand side results in
@@t 1Z Ä 1
@du
u.x;t/dx D ˇ D 0:
1 @x 1
(Recall that
constant during
@u=@xthe D
simulation.
0 at the end
Giving
points.)
the The
PDEresult
an interpretation
means that R
udx remains
in11 terms
of heat
conduction can easily explain the result: with Neumann conditions no heat can
escape from the domain so the initial heat will just be evenly distributed, but not leak
out, so the temperature cannot go to zero (or the scaled and translated temperature
u, to be precise). The area under the initial condition is 1, so with a sufficiently fine
mesh, u ! 1, regardless of .
as
numpy.dot(x[:-1], y[1:]), or more explicitly as numpy.sum(x[:-1]*y[1:]).
Filename: polyarea_vec.py.
ˇu00.x/ D f.x/;
which is known as a two-point boundary value problem. This is nothing but the
stationary limit of the diffusion problem in Sect. 5.1.4. How can we solve such
a stationary problem (5.38)? The simplest strategy, when we already have a solver
for the corresponding time-dependent problem, is to use that solver and simulate
until t ! 1, which in practice means that u.x;t/ no longer changes in time (within
some tolerance).
A nice feature of implicit methods like the Backward Euler scheme is that one
can take one very long time step to “infinity” and produce the solution of (5.38).
a) Let (5.38) be valid at mesh points xi in space, discretize u00 by a finite difference,
and set up a system of equations for the point values ui,i D 0;:::;N, where ui
is the approximation at mesh point xi.
b) Show that if t ! 1 in (5.16) - (5.18), it leads to the same equations as in a).
c) Demonstrate, by running a program, that you can take one large time step with
the Backward Euler scheme and compute the solution of (5.38). The solution is
very boring since it is constant: u.x/ D C.
Filename: rod_stationary.py.
Remarks If the interest is in the stationary limit of a diffusion equation, one can
either solve the associated Laplace or Poisson equation directly, or use a Backward
Euler scheme for the time-dependent diffusion equation with a very long time step.
Using a Forward Euler scheme with small time steps is typically inappropriate in
5.2 Exercises 183
such situations because the solution changes more and more slowly, but the time
step must still be kept small, and it takes “forever” to approach the stationary state.
This is yet another example why one needs implicit methods like the Backward
Euler scheme.
Hint Do Exercise 5.9. Modify the boundary condition in the code so it incorporates
a known value for u.1/.
Filename: 2ptBVP.py.
Open Access This chapter is distributed under the terms of the Creative Commons Attribution
NonCommercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/),
which permits any noncommercial use, duplication, adaptation, distribution and reproduction
in any medium or format, as long as you give appropriate credit to the original author(s) and the
source, a link is provided to the Creative Commons license and any changes made are indicated.
The images or other third party material in this chapter are included in the work’s Creative
Commons license, unless indicated otherwise in the credit line; if such material is not included
in the work’s Creative Commons license and the respective action is not permitted by statutory
regulation, users will need to obtain permission from the license holder to duplicate, adapt or
reproduce the material.
Solving Nonlinear Algebraic Equations
6
As a reader of this book you are probably well into mathematics and often “ac
cused” of being particularly good at “solving equations” (a typical comment at
family dinners!). However, is it really true that you, with pen and paper, can solve
many types of equations? Restricting our attention to algebraic equations in one
unknown x, you can certainly do linear equations: ax Cb D 0, and quadratic ones:
ax2 C bx C c D 0. You may also know that there are formulas for the roots of cu
bic and quartic equations too. Maybe you can do the special trigonometric equation
sinx C cosx D 1 as well, but there it (probably) stops. Equations that are not re
ducible to one of the mentioned cannot be solved by general analytical techniques,
which means that most algebraic equations arising in applications cannot be treated
with pen and paper!
If we exchange the traditional idea of finding exact solutions to equations with
the idea of rather finding approximate solutions, a whole new world of possibilities
opens up. With such an approach, we can in principle solve any algebraic equation.
Let us start by introducing a common generic form for any algebraic equation:
f.x/ D 0:
Here, f.x/ is some prescribed formula involving x. For example, the equation
ex sin x D cosx 185
has
f.x/ D e x sinx cosx :
Just move all terms to the left-hand side and then the formula to the left of the
equality sign is f.x/.
So, when do we really need to solve algebraic equations beyond the simplest
types we can treat with pen and paper? There are two major application areas. One
is when using implicit numerical methods for ordinary differential equations. These
give rise to one or a system of algebraic equations. The other major application type
is optimization, i.e., finding the maxima or minima of a function. These maxima and
minima are normally found by solving the algebraic equation F0.x/ D 0 if F.x/ is
the function to be optimized. Differential equations are very much used throughout
science and engineering, and actually most engineering problems are optimization
problems in the end, because one wants a design that maximizes performance and
minimizes cost.
We first consider one algebraic equation in one variable, with our usual emphasis
on how to program the algorithms. Systems of nonlinear algebraic equations with
many variables arise from implicit methods for ordinary and partial differential
equations as well as in multivariate optimization. Our attention will be restricted to
Newton’s method for such systems of nonlinear algebraic equations.
Terminology
When solving algebraic equations f.x/ D 0, we often say that the solution x
is a root of the equation. The solution process itself is thus often called root
finding.
Assume that we have a set of points along the curve of a function f.x/:
We want to solve f.x/ D 0, i.e., find the points x where f crosses the x axis.
A brute force algorithm is to run through all points on the curve and check if one
point is below the x axis and if the next point is above the x axis, or the other way
around. If this is found to be the case, we know that f must be zero in between
these two x points.
f.xiC1/f.xi/x
iC1 yiC1y
iC1 ix
f.x/ xi .xxi/C f.xi/ D .xxi/Cyi;
x i
x = linspace(0, 4, 10001)
y = f(x)
if root
print
is’Could
None: not find any root in [%g, %g]’ % (x[0], x[-1])
else:
print ’Find (the first) root as x=%g’ % root
(See
Note
the file
the brute_force_root_finder_flat.py.)
nice use of setting root to None: we can simply test if root is
None to see if we found a root and overwrote the None value, or if we did not find
any root among the tested points.
Running this program with some function, say f.x/ D ex2 cos.4x/ (which has
Increasing
a solution at
thex number
D 8), gives the root 0.392701, which has an error of 1:9 106.
of points with a factor of ten gives a root with an error of
2:4 108.
After such a quick “flat” implementation of an algorithm, we should always try
to offer the algorithm as a Python function, applicable to as wide a problem domain
as possible. The function should take f and an associated interval Œa;b as input, as
well as a number of points (n), and return a list of all the roots in Œa;b. Here is our
candidate for a good implementation of the brute force rooting finding algorithm:
(See
This
thetime
file brute_force_root_finder_function.py.)
we use another elegant technique to indicate if roots were found or not:
roots is an empty list if the root finding was unsuccessful, otherwise it contains all
the roots. Application of the function to the previous example can be coded as
roots
lambda x: exp(-x**2)*cos(4*x), 0, 4, 1001)
= brute_force_root_finder(
if roots:
print roots
else:
print ’Could not find any roots’
6.1 Brute Force Methods 189
The max and min functions are standard Python functions for finding the maxi
mum and minimum element of a list or an object that one can iterate over with a for
loop.
190 6 Solving Nonlinear Algebraic Equations
minima,
lambda
maxima
x: exp(-x**2)*cos(4*x), 0, 4, 1001)
= brute_force_optimizer(
We shall consider the very simple problem of finding the square root of 9, which
is the positive solution of x2 D 9. The nice feature of solving an equation whose
solution is known beforehand is that we can easily investigate how the numerical
method and the implementation perform in the search for the solution. The f.x/
function corresponding to the equation x2 D9 is
f.x/ D x2 9:
Our interval of interest for solutions will be Œ0; 1000 (the upper limit here is chosen
somewhat arbitrarily).
In the following, we will present several efficient and accurate methods for solv
ing nonlinear algebraic equations, both single equation and systems of equations.
The methods all have in common that they search for approximate solutions. The
methods differ, however, in the way they perform the search for solutions. The idea
for the search influences the efficiency of the search and the reliability of actually
finding a solution. For example, Newton’s method is very fast, but not reliable,
while the bisection method is the slowest, but absolutely reliable. No method is
best at all problems, so we need different methods for different problems.
Figure 6.1 shows the f.x/ function in our model equation x2 9 D 0. Numer
ical methods for algebraic equations require us to guess at a solution first. Here,
this guess is called x0. The fundamental idea of Newton’s method is to approxi
mate the original function f.x/ by a straight line, i.e., a linear function, since it
is straightforward to solve linear equations. There are infinitely many choices of
how to approximate f.x/ by a straight line. Newton’s method applies the tangent
of f.x/ at x0, see the rightmost tangent in Fig. 6.1. This linear tangent function
crosses the x axis at a point we call x1. This is (hopefully) a better approximation
to the solution of f.x/ D 0 than x0. The next fundamental idea is to repeat this
process. We find the tangent of f at x1, compute where it crosses the x axis, at
a point called x2, and repeat the process again. Figure 6.1 shows that the process
brings us closer and closer to the left. It remains, however, to see if we hit x D 3 or
come sufficiently close to this solution.
function,
How do here
wecalled
compute
fQ.x/,
theistangent
linear and
of ahas
function
two properties:
f.x/ at a point x0? The tangent
Fig. 6.1 Illustrates the idea of Newton’s method with f.x/ D x2 9, repeatedly solving for
crossing of tangent lines with the x axis
192 6 Solving Nonlinear Algebraic Equations
f0.xif0/we
So, and
write
fQ.xthe
0/ D
tangent
f.x0/,function
resulting fQ.x/ D ax C b, we must require fQ0.x0/ D
asin
which
Themeans
key step
solving
in Newton’s
fQ.x/ Dmethod
0: is to find where the tangent crosses the x axis,
fQ.x/ D 0 ) x D x0 f.x0/
:
f0.x0/
This is our new candidate point, which we call x1:
D x0f.x0/f0.x
x1 :
0/
def f(x):
return x**2 - 9
def dfdx(x):
return 2*x
f.xn/
xnC1 D xn :
f0.xn /
Seeing such an index, many would implement this as
Such an array is fine, but requires storage of all the approximations. In large
industrial applications, where Newton’s method solves millions of equations at
once, one cannot afford to store all the intermediate approximations in memory,
so then it is important to understand that the algorithm in Newton’s method has
no more need for xn when xnC1 is computed. Therefore, we can work with one
variable x and overwrite the previous value:
x = x - f(x)/dfdx(x)
500.0045
250.011249919
125.02362415
62.5478052723
31.3458476066
15.816483488
8.1927550496
4.64564330569
3.2914711388
3.01290538807
3.00002763928
We clearly see that the iterations approach the solution quickly. This speed of
the search for the solution is the primary strength of Newton’s method compared to
other methods.
The naive_Newton function works fine for the example we are considering here.
However, for more general use, there are some pitfalls that should be fixed in an
improved version of the code. An example may illustrate what the problem is: let
us solve tanh.x/ D 0, which has solution x D 0. With jx0j Ä 1:08 everything
works fine. For example, x0 leads to six iterations if D 0:001:
194 6 Solving Nonlinear Algebraic Equations
-1.05895313436
0.989404207298
-0.784566773086
0.36399816111
-0.0330146961372
2.3995252668e-05
-1.09331618202
1.10490354324
-1.14615550788
1.30303261823
-2.06492300238
13.4731428006
-1.26055913647e+11
is 1.0
Thetodivision
machinebyprecision,
zero is caused
and by
then
x7fD0.x/1:26055913647
D 1 tanh.x/21011,becomes
becausezero
tanh.x7/
in the
denominator in Newton’s method.
The underlying problem, leading to the division by zero in the above example,
is that Newton’s method diverges: the approximations move further and further
away from x D 0. If it had not been for the division by zero, the condition in
the while loop would always be true and the loop would run forever. Divergence
of Newton’s method occasionally happens, and the remedy is to abort the method
when a maximum number of iterations is reached.
Another disadvantage of the naive_Newton function is that it calls the f.x/
function twice as many times as necessary. This extra work is of no concern when
f.x/ is fast to evaluate, but in large-scale industrial software, one call to f.x/ might
take hours or days, and then removing unnecessary calls is important. The solution
in our function is to store the call f(x) in a variable (f_value) and reuse the value
instead of making a new call f(x).
To summarize, we want to write an improved function for implementing New
ton’s method where we
A more robust and efficient version of the function, inserted in a complete program
Newtons_method.pyfor solving x2 9 D 0, is listed below.
except ZeroDivisionError:
print "Error! - derivative zero for x = ", x
sys.exit(1) # Abort with error
f_value = f(x)
iteration_counter += 1
def f(x):
return x**2 - 9
def dfdx(x):
return 2*x
Python
Handling
tries to
of run
the potential
the code division
in the try
byblock.
zero isIfdone
anything
by atry-exceptconstruction.
goes wrong here, or more
solution and the number of function calls. The main cost of a method for solving
f.x/ D 0 equations is usually the evaluation of f.x/ and f0.x/, so the total num
ber of calls to these functions is an interesting measure of the computational work.
Note that in function Newton there is an initial call to f.x/ and then one call to f
and one to f0 in each iteration.
Running Newtons_method.py, we get the following printout on the screen:
As we did with the integration methods in Chapter 3, we will collect our solvers
for nonlinear
easy import
algebraic
and use.equations
The firstinfunction
a separate
placed
file named
in this nonlinear_solvers.py
file is then Newton.
The Newton scheme will work better if the starting value is close to the solution.
A good starting value may often make the difference as to whether the code actually
finds a solution or not. Because of its speed, Newton’s method is often the method
of first choice for solving nonlinear algebraic equations, even if the scheme is not
guaranteed to work. In cases where the initial guess may be far from the solution,
a good strategy is to run a few iterations with the bisection method (see Chapter 6.4)
to narrow down the region where f is close to zero and then switch to Newton’s
method for fast convergence to the solution.
Newton’s method requires the analytical expression for the derivative f0.x/.
Derivation of f0.x/ is not always a reliable process by hand if f.x/ is a com
plicated function. However, Python has the symbolic package SymPy, which we
may use to create the required dfdx function. In our sample problem, the recipe
goes as follows:
The nice feature of this code snippet is that dfdx_expr is the exact analytical
expression for the derivative, 2*x, if you print it out. This is a symbolic expression
so we cannot do numerical computing with it, but the lambdify constructions turn
symbolic expressions into callable Python functions.
The next method is the secant method, which is usually slower than Newton’s
method, but it does not require an expression for f0.x/, and it has only one function
call periteration.
6.3 The Secant Method 197
Fig.6.2 Illustrates the use of secants in the secant method when solving x29 D 0; x 2 Œ0; 1000.
From two chosen starting values, x0 D 1000 and x1 D 700 the crossing x2 of the corresponding
secant with the x axis is computed, followed by a similar computation of x3 from x1 and x2
1https://en.wikipedia.org/wiki/Secant_line
198 6 Solving Nonlinear Algebraic Equations
Comparing (6.3) to the graph in Fig. 6.2, we see how two chosen starting points
(x0 D 1000, x1 D 700, and corresponding function values) are used to compute
x2. Once we have x2, we similarly use x1 and x2 to compute x3. As with Newton’s
method, the procedure is repeated until f.xn/ is below some chosen limit value,
or some limit on the number of iterations has been reached. We use an iteration
counter here too, based on the same thinking as in the implementation of Newton’s
method.
We can store the approximations xn in an array, but as in Newton’s method,
we notice that the computation of xnC1 only needs knowledge of xn and xn 1, not
“older” approximations. Therefore, we can make use of only three variables: x for
xnC1, x1 for xn, and x0 for xn1. Note that x0 and x1 must be given (guessed) for
the algorithm to start.
A program secant_method.pythat solves our example problem may be written
as:
def f(x):
return x**2 - 9
x0 = 1000; x1 = x0 - 1
The number of function calls is now related to no_iterations, i.e., the number
of iterations, as 2 + no_iterations, since we need two function calls before en
tering the while loop, and then one function call per loop iteration. Note that, even
though we need two points on the graph to compute each updated estimate, only
6.4 The Bisection Method 199
a single function call (f(x1)) is required in each iteration since f(x0) becomes the
“old” f(x1) and may simply be copied as f_x0 = f_x1 (the exception is the very
first iteration where two function evaluations are needed).
Running secant_method.py, gives the following printout on the screen:
py As
forwith
easythe
import
function
and use
Newton,
later. we place secantin the file nonlinear_solvers.
Neither Newton’s method nor the secant method can guarantee that an existing so
lution will be found (see Exercises 6.1 and 6.2). The bisection method, however,
does that. However, if there are several solutions present, it finds only one of them,
just as Newton’s method and the secant method. The bisection method is slower
than the other two methods, so reliability comes with a cost of speed.
To solve x2 9 D 0, x 2 Œ0; 1000, with the bisection method, we reason as
follows. The first key idea is that if f.x/ D x29 is continuous on the interval and
the function values for the interval endpoints (xL D 0, xR D 1000) have opposite
signs, f.x/ must cross the x axis at least once on the interval. That is, we know
there is at least one solution.
The second key idea comes from dividing the interval in two equal parts, one
to the left and one to the right of the midpoint xM D 500. By evaluating the sign
of f.xM/, we will immediately know whether a solution must exist to the left or
right of xM. This is so, since if f.xM/ 0, we know that f.x/ has to cross the x
axis between xL and xM at least once (using the same argument as for the original
interval). Likewise, if instead f.xM/ Ä 0, we know that f.x/ has to cross the x
axis between xM and xR at least once.
In any case, we may proceed with half the interval only. The exception is if
f.xM/ 0, in which case a solution is found. Such interval halving can be
continued until a solution is found. A “solution” in this case, is when jf.xM/j
is sufficiently close to zero, more precisely (as before): jf.xM/j < , where is
a small number specified by the user.
The sketched strategy seems reasonable, so let us write a reusable function that
can solve a general algebraic equation f.x/ D 0 (bisection_method.py):
if return_x_list:
x_list = []
if return_x_list:
return x_list, iteration_counter
else:
return x_M, iteration_counter
def f(x):
return x**2 - 9
a = 0; b = 1000
Note that we first check if f changes sign in Œa;b, because that is a requirement
for the algorithm to work. The algorithm also relies on a continuous f.x/ function,
but this is very challenging for a computer code to check.
We get the following printout to the screen when bisection_method.pyis run:
We notice that the number of function calls is much higher than with the previous
methods.
jb aj
2n ; (6.4)
because the initial interval has been halved n times. Therefore, to meet a toler
ance , we need n iterations such that the length of the current interval equals
:
jb 2n aj D ) n D ln..bln2a/=/
:
6.5 Rate of Convergence 201
As with the two previous methods, the function bisection is placed in the file
nonlinear_solvers.pyfor easy import and use.
With the methods above, we noticed that the number of iterations or function calls
could differ quite substantially. The number of iterations needed to find a solution
is closely related to the rate of convergence, which dictates the speed of error re
duction as we approach the root. More precisely, we introduce the error in iteration
n as en D jx xnj, and define the convergence rate q as
enC1 D Ceqn; (6.5)
where C is a constant. The exponent q measures how fast the error is reduced from
one iteration to the next. The larger q is, the faster the error goes to zero, and the
fewer iterations we need to meet the stopping criterion jf.x/j < .
A single q in (6.5) is defined in the limit n ! 1. For finite n, and especially
smaller n, q will vary with n. To estimate q, we can compute all the errors en and
set up (6.5) for three consecutive experiments n 1, n, and n C 1:
en D Cenq
1;
enC1 D Ceqn:
Dividing these two equations by each other and solving with respect to q gives
ln.enC1=en/
qD :
ln.en=en 1/
Since this q will vary somewhat with n, we call it qn. As n grows, we expect qn
to approach a limit (qn ! q). To compute all the qn values, we need all the xn
approximations. However, our previous implementations of Newton’s method, the
secant method, and the bisection method returned just the final approximation.
Therefore, we have extended the implementations in the module file
nonlinear_solvers.py such that the user can choose whether the final value
or the whole history of solutions is to be returned. Each of the extended im
plementations now takes an extra parameter return_x_list. This parameter is
a boolean, set to True if the function is supposed to return all the root approxima
tions, or False, if the function should only return the final approximation. As an
example, let us take a closer look at Newton:
f_value = f(x)
iteration_counter += 1
if return_x_list:
x_list.append(x)
if return_x_list:
return x_list, iteration_counter
else:
return x, iteration_counter
The
We can
function
now make
is found
a call
in the file nonlinear_solvers.py.
and get a list x returned. With knowledge of the exact solution x of f.x/ D 0
we can compute all the errors en and all the associated qn values with the compact
function
The error model (6.5) works well for Newton’s method and the secant method.
For the bisection method, however, it works well in the beginning, but not when the
solution is approached.
We can compute the rates qn and print them nicely,
Newton:
1.01 1.02 1.03 1.07 1.14 1.27 1.51 1.80 1.97 2.00
6.6 Solving Multiple Nonlinear Algebraic Equations 203
1.26 0.93 1.05 1.01 1.04 1.05 1.08 1.13 1.20 1.30 1.43
secant:
Remark If we in the bisection method think of the length of the current interval
from
containing
12en, i.e.,
a sketch
q the
D that
1solution
and
this
C error
as
D the
12 can
, but
error
oscillate
if en
en, isthen
the
between
(6.5)
true error
the
works
current
jxperfectly
xnj,
interval
since enC1 D
it is easily seen
length and
a potentially very small value as we approach the exact solution. The corresponding
rates qn fluctuate widely and are of no interest.
F0.x0;x1;:::;xn/ D 0; (6.6)
F1.x0;x1;:::;xn/ D 0; (6.7)
:
:: D ::: (6.8)
Fn.x0;x1;:::;xn/ D 0: (6.9)
(6.10)
F D .F0;:::;F1/; x D .x0;:::;xn/:
x2 D y x cos.x/ (6.11)
yx C e y D x1 (6.12)
204 6 Solving Nonlinear Algebraic Equations
Fo(x0, x) = x* – y + x cos(tx) = 0,
F(x0, x) = yx + et' – XT = 0.
We follow the ideas of Newton’s method for one equation in one variable: approxi
mate the nonlinear f by a linear function and find the root of that function. When
n variables are involved, we need to approximate a vector function F(x) by some
linear function F = Jr + c, where J is an n x n matrix and c is some vector of
length n.
The technique for approximating F by a linear function is to use the first two
terms in a Taylor series expansion. Given the value of F and its partial derivatives
with respect to x at some point x;, we can approximate the value at some point
x;11 by the two first term in a Taylor series expansion around x i:
The next terms in the expansions are omitted here and of size ||x||11 – c ||*, which
are assumed to be small compared with the two terms above.
The expression VF is the matrix of all the partial derivatives of F. Component
(i,j) in VF is
ôF,
ox;
For example, in our 2 × 2 system (6.11)–(6.12) we can use Sympy to compute the
Jacobian:
VF = (# #) (
dFL
6x0
OFL
0x1
-
| T
2x0 + cos(ſt Yo) – ſtro sin(ſtro)
x1 + xo–2
–1
-
30 — e
—x1
)
The matrix VF is called the Jacobian of F and often denoted by J.
6.6 Solving Multiple Nonlinear Algebraic Equations 205
The idea of Newton’s method is that we have some approximation xi to the root and
seek a new (and hopefully better) approximation xiC1 by approximating F.xiC1/
by a linear function and solve the corresponding linear system of algebraic equa
tions. We approximate the nonlinear problem F.xiC1/ D 0 by the linear problem
where J.xi/ is just another notation for rF.xi/. The equation (6.13) is a linear
system with coefficient matrix J and right-hand side vector F.xi/. We therefore
write this system in the more familiar form
J.xi/ı D F.xi/;
where we have introduce a symbol ı for the unknown vector xiC1 xi that multi
plies the Jacobian J.
The i-th iteration of Newton’s method for systems of algebraic equations con
sists of two steps:
Solving systems of linear equations must make use of appropriate software. Gaus
sian elimination is the most common, and in general the most robust, method for
this purpose. Python’s numpy package has a module linalg that interfaces the
well-known LAPACK package with high-quality and very well tested subroutines
for linear algebra. The statement x = numpy.linalg.solve(A, b) solves a sys
tem Ax D b with a LAPACK method based on Gaussian elimination.
When nonlinear systems of algebraic equations arise from discretization of par
tial differential equations, the Jacobian is very often sparse, i.e., most of its elements
are zero. In such cases it is important to use algorithms that can take advantage of
the many zeros. Gaussian elimination is then a slow method, and (much) faster
methods are based on iterative techniques.
6.6.4 Implementation
import numpy as np
F_value = F(x)
F_norm = np. linalg. norm (F_value, ord=2) # 12 norm of vector
iteration_counter = 0
while abs (F_norm) > eps and iteration_counter × 100:
delta = np. linalg. solve (J (x), -F_value)
x = x + delta
F_value = F(x)
F_norm = mp.linalg. norm (F_value, ord=2)
iteration_counter += 1
def F(x):
return np. array (
[x [O]++2 − x [1] + x [O]+cos(piºx [0]),
x [O]+x [1] + exp (-x [1]) - x [O]++ (-1).])
def J (x):
return np. array (
[[2+x [O] + cos (piºx [O] ) – piºx [O]+sin (piºx [0]), -1],
[x [1] + x [O]++ (–2), x [O] – exp(-x [1]).]])
Here, the testing is based on the L2 norm of the error vector. Alternatively, we
could test against the values of x that the algorithm finds, with appropriate toler
ances. For example, as chosen for the error norm, if eps-0.0001, a tolerance of
10 * can be used for x [O] and x [1].
6.7 Exercises
Filename: secant_failure.*.
ˇ4 D !2 EI
%A
;
where % is the density of the beam, A is the area of the cross section, E is Young’s
modulus, and I is the moment of the inertia of the cross section. The most important
parameter of interest is !, which is the frequency of the beam. We want to compute
the frequencies of a vibrating steel beam with a rectangular cross section having
width b D 25 mm and height h D 8 mm. The density of steel is 7850 kg/m3, and
E D 21011 Pa. The moment of inertia of a rectangular cross section is I D bh3=12.
a) Plot the equation to be solved so that one can inspect where the zero crossings
occur.
Hint When writing the equation as f.ˇ/ D 0, the ffunction increases its ampli
tude dramatically with ˇ. It is therefore wise to look at an equation with damped
amplitude, g.ˇ/ D eˇf .ˇ/ D 0. Plot g instead.
Filename: beam_vib.py.
Open Access This chapter is distributed under the terms of the Creative Commons Attribution
NonCommercial 4.0 International License (http://creativecommons.org/licenses/by-nc/4.0/),
which permits any noncommercial use, duplication, adaptation, distribution and reproduction
in any medium or format, as long as you give appropriate credit to the original author(s) and the
source, a link is provided to the Creative Commons license and any changes made are indicated.
The images or other third party material in this chapter are included in the work’s Creative
Commons license, unless indicated otherwise in the credit line; if such material is not included
in the work’s Creative Commons license and the respective action is not permitted by statutory
regulation, users will need to obtain permission from the license holder to duplicate, adapt or
reproduce the material.
Getting Access to Python
A
This appendix describes different technologies for either installing Python on your
own computer or accessing Python in the cloud. Plain Python is very easy to install
and use in cloud services, but for this book we need many add-on packages for doing
scientific computations. Python together with these packages constitute a complex
software eco system that is non-trivial to build, so we strongly recommend to use
one of the techniques described next1.
The strictly required software packages for working with this book are
1 Some of the text is taken from the 4th edition of the book A Primer on Scientifi Programming
with Python, by H. P. Langtangen, published by Springer, 2014.
2http://python.org
3http://www.numpy.org
4http://matplotlib.org
5http://ipython.org
6https://github.com/hplgit/scitools
7 http://starship.python.net/crew/hinsen
8http://pytest.org/latest/
9https://nose.readthedocs.org
10http://www.pip-installer.org
11http://cython.org
© The
S. Linge,
Author(s)
H.P. Langtangen,
2016 Programming for Computations – Python, 209
Python 2 or 3?
Python comes in two versions, version 2 and 3, and these are not fully compati
ble. However, for the programs in this book, the differences are very small, the
major one being print, which in Python 2 is a statement like
The authors have written Python v2.7 code in this book in a way that makes
porting to version 3.4 or later trivial: most programs will just need a fix of the
print statement. This can be automatically done by running 2to3 prog.py to
transform a Python 2 program prog.py to its Python 3 counterpart. One can
also use tools like future or six to easily write programs that run under both
versions 2 and 3, or the futurize program can automatically do this for you
based on v2.7 code.
Since many tools for doing scientific computing in Python are still only avail
able for Python version 2, we use this version in the present book, but emphasize
that it has to be v2.7 and not older versions.
There are different ways to get access to Python with the required packages:
A system administrator can take the list of software packages and install the missing
ones on a computer system. For the two other options, detailed descriptions are
given below.
Using a web service is very straightforward, but has the disadvantage that you
are constrained by the packages that are allowed to install on the service. There are
services at the time of this writing that suffice for working with most of this book,
but if you are going to solve more complicated mathematical problems, you will
need more sophisticated mathematical Python packages, more storage and more
computer resources, and then you will benefit greatly from having Python installed
on your own computer.
12http://sympy.org
13http://scipy.org
A.2 Anaconda and Spyder 211
Anaconda installs the pip tool that is handy to install additional packages. In a Ter
minal application on Mac or in a PowerShell terminal on Windows, write
Terminal
You have basically three choices to develop and test a Python program:
The IPython notebook is briefly descried in Sect. A.5, while the other two options
are outlined below.
14https://store.continuum.io/cshop/anaconda/
212 A Getting Access to Python
Since programs consist of plain text, we need to write this text with the help of
another program that can store the text in a file. You have most likely extensive
experience with writing text on a computer, but for writing your own programs you
need special programs, called editors, which preserve exactly the characters you
type. The widespread word processors, Microsoft Word being a primary example,
are aimed at producing nice-looking reports. These programs format the text and
are not acceptable tools for writing your own programs, even though they can save
the document in a pure text format. Spaces are often important in Python programs,
and editors for plain text give you complete control of the spaces and all other
characters in the program file.
The most widely used editors for writing programs are Emacs and Vim, which are
available on all major platforms. Some simpler alternatives for beginners are
Linux: Gedit
Mac OS X: TextWrangler
Windows: Notepad++
We may mention that Python comes with an editor called Idle, which can be used
to write programs on all three platforms, but running the program with command
line arguments is a bit complicated for beginners in Idle so Idle is not my favorite
recommendation.
Gedit is a standard program on Linux platforms, but all other editors must be
installed in your system. This is easy: just google the name, download the file, and
follow the standard procedure for installation. All of the mentioned editors come
with a graphical user interface that is intuitive to use, but the major popularity of
Emacs and Vim is due to their rich set of short-keys so that you can avoid using the
mouse and consequently edit at higher speed.
To run the Python program, you need a terminal window. This is a window where
you can issue Unix commands in Linux and Mac OS X systems and DOS com
mands in Windows. On a Linux computer, gnome-terminal is my favorite, but
other choices work equally well, such as xterm and konsole. On a Mac computer,
launch the application Utilities - Terminal. On Windows, launch PowerShell.
You must first move to the right folder using the cd foldername command.
Whatever
Then running
the program
a pythonprints
program
canprog.py
be seen inisthe
a matter
terminal
of writing
window.python prog.py.
A.3 How to Write and Run a Python Program 213
1. Create a folder where your Python programs can be located, say with name
mytest under your home folder. This is most conveniently done in the terminal
window since you need to use this window anyway to run the program. The
command for creating a new folder is mkdir mytest.
2. Move to the new folder: cd mytest.
3. Start the editor of your choice.
4. Write a program in the editor, e.g., just the line print ’Hello!’. Save the
program under the name myprog1.py in the mytest folder.
5. Move to the terminal window and write python myprog1.py. You should see
the word Hello! being printed in the window.
A.3.5 Spyder
Choosing Run – Run now leads to a separate window with a plot of the function
ex sin.x/. Figure A.1 shows how the Spyder application may look like.
The plot file we generate in the above program, tmp.png, is by default found
in the Spyder folder listed in the default text in the top of the program. You can
choose Run – Configure ... to change this folder as desired. The program you
write is written to a file .temp.py in the same default folder, but any name and
folder can be specified in the standard File – Save as... menu.
A convenient feature of Spyder is that the upper right window continuously dis
plays documentation of the statements you write in the editor to the left.
214 A Getting Access to Python
You can avoid installing Python on your machine completely by using a web service
that allows you to write and run Python programs. Computational science projects
will normally require some kind of visualization and associated graphics packages,
which is not possible unless the service offers IPython notebooks. There are two
excellent web services with notebooks: SageMathCloud at https://cloud.sagemath.
com/ and Wakari at https://www.wakari.io/wakari. At both sites you must create an
account before you can write notebooks in the web browser and download them to
your own computer.
Sign in, click on New Project, give a title to your project and decide whether it
should be private or public, click on the project when it appears in the browser,
and click on Create or Import a File, Worksheet, Terminal or Directory.... If your
Python program needs graphics, you need to choose IPython Notebook, otherwise
you can choose File. Write the name of the file above the row of buttons. As
suming we do not need any graphics, we create a plain Python file, say with name
py1.py. By clicking File you are brought to a browser window with a text editor
where you can write Python code. Write some code and click Save. To run the
program, click on the plus icon (New), choose Terminal, and you have a plain Unix
terminal window where you can write python py1.py to run the program. Tabs
over the terminal (or editor) window make it easy to jump between the editor and
the terminal. To download the file, click on Files, point on the relevant line with the
file, and a download icon appears to the very right. The IPython notebook option
works much in the same way, see Sect. A.5.
A.5 Writing IPython Notebooks 215
After having logged in at the wakari.io site, you automatically enter an IPython
notebook with a short introduction to how the notebook can be used. Click on
the New Notebook button to start a new notebook. Wakari enables creating and
editing plain Python files too: click on the Add file icon in pane to the left, fill in the
program name, and you enter an editor where you can write a program. Pressing
Execute launches an IPython session in a terminal window, where you can run the
program by run prog.py if prog.py is the name of the program. To download
the file, select test2.py in the left pane and click on the Download file icon.
There is a pull-down menu where you can choose what type of terminal window
you want: a plain Unix shell, an IPython shell, or an IPython shell with Matplotlib
for plotting. Using the latter, you can run plain Python programs or commands with
graphics. Just choose the type of terminal and click on +Tab to make a new terminal
window of the chosen type.
Both SageMathCloud and Wakari let you install your own Python packages. To
install any package packagename available at PyPi15, run
Terminal
To install the SciTools package, which is useful when working with this book, run
the command
Terminal
The IPython notebook is a splendid interactive tool for doing science, but it can
also be used as a platform for developing Python code. You can either run it
locally on your computer or in a web service like SageMathCloud or Wakari. In
stallation on your computer is trivial on Ubuntu, just sudo apt-get install
ipython-notebook, and also on Windows and Mac16 by using Anaconda or En
thought Canopy for the Python installation.
The interface to the notebook is a web browser: you write all the code and see
all the results in the browser window. There are excellent YouTube videos on how
to use the IPython notebook, so here we provide a very quick “step zero” to get
anyone started.
15https://pypi.python.org/pypi
16http://ipython.org/install.html
216 A Getting Access to Python
g = 9.81
v0 = 5
t = v0*t
y 0.6 - 0.5*g*t**2
in a cell and run the cell by clicking on Run Selected (notebook running locally on
your machine) or on the “play” button (notebook running in the cloud). This action
will execute the Python code and initialize the variables g, v0, t, and y. You can
then write print y in a new cell, execute that cell, and see the output of this state
ment in the browser. It is easy to go back to a cell, edit the code, and re-execute it.
To download the notebook to your computer, choose the File – Download as
menu and select the type of file to be downloaded: the original notebook format
(.ipynb file extension) or a plain Python program version of the notebook (.py file
extension).
The real strength of IPython notebooks arises when you want to write a report to
document how a problem can be explored and solved. As a teaser, open a new
notebook, click in the first cell, and choose Markdown as format (notebook running
locally) or switch from Code to Markdown in the pull-down menu (notebook in
the cloud). The cell is now a text field where you can write text with Markdown17
ematicsMathematics
syntax. and an equation
canonbeaentered
separate LATEX code. Try some text with inline math
asline:
$$
f(x) = e^{-x}\sin (2\pi x),\quad x\in [0, 4]
$$
Execute the cell and you will see nicely typeset mathematics in the browser. In the
new cell, add some code to plot f.x/:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline # make plots inline in the notebook
x = np.linspace(0, 4, 101)
y = np.exp(-x)*np.sin(2*pi*x)
plt.plot(x, y, ’b-’)
plt.xlabel(’x’); plt.ylabel(’y’)
17http://daringfireball.net/projects/markdown/syntax
A.5 Writing IPython Notebooks 217
Executing these statements results in a plot in the browser, see Fig. A.2. It was
popular to start the notebook by ipython notebook –pylabto import everything
from numpy and matplotlib.pyplot and make all plots inline, but the –pylab
option is now officially discouraged18. If you want the notebook to behave more as
MATLAB and not use the np and plt prefix, you can instead of the first three lines
above write %pylab.
18http://carreau.github.io/posts/10-No-PyLab-Thanks.ipynb.html
References
219
220 7 References
20. T. E. Oliphant et al. NumPy array processing package for Python. http://www.numpy.org.
21. S. Otto and J. P. Denier. An Introduction to Programming and Numerical Methods in MATLAB.
Springer, 2005.
22. F. Perez and B. E. Granger. IPython: a system for interactive scientific computing. Computing
in Science & Engineering, 9, 2007.
23. F. Perez, B. E. Granger, et al. IPython software package for interactive scientific computing.
http://ipython.org/.
24. W. H. Press, S. A. Teukolsky, W. T. Vetterling, and B. P. Flannery. Numerical Recipes. Cam
bridge, 1992. http://www.nrbook.com/a/bookcpdf.php.
25. Python programming language. http://python.org.
26. G. Recktenwald. Numerical Methods with MATLAB: Implementations and Applications.
Prentice-Hall, 2000. http://web.cecs.pdx.edu/~gerry/nmm/.
27. G. Sewell. The Numerical Solution of Ordinary and Partial Differential Equations. Wiley,
2005.
28. T. Siauw and A. Bayen. An Introduction to MATLAB Programming and Numerical Meth
ods for Engineers. Academic Press, 2014. http://www.sciencedirect.com/science/book/
9780124202283.
29. L. N. Trefethen. Spectral Methods in MATLAB. SIAM, 2000.
30. L. N. Trefethen. Approximation Theory and Approximation Practice. SIAM, 2012.
31. T. Young and M. J. Mohlenkamp. Introduction to Numerical Methods and MATLAB Program
ming for Engineers. 2015. https://www.math.ohiou.edu/courses/math3600/book.pdf.
Index
221
222 7 Index
eexception
E
else,
Emacs,
error
Euler
Euler’s
exit
exp
execute
lif,pi,
asymptotic,
function
message,
rounding,
tolerance,
math
(sys),
29
2948
method,
6,
(anotation,
handling,
212
200
program),
(erf),
21
7170
101
64
98
22
3 Heun’s method, 131
hold (on/off), 19
I
Idle, 212
if, 29
implement (a program), 3
implementation
general, 60
specific, 60
import, 12
math, 8
matplotlib.pyplot, 10
module, 62
numpy, 10
random (function), 30
indent, 29
indexing
F one based, 17
False, 29 zero based, 17
fast code, 25 initial conditions, 162
finite difference method, 99 input, 23
finite precision (of float), 71 instability, 170
flat program, 63 instruction, 4
float, 13 int, 13
floating point number (float), 71 integer division, 14
for loop, 37 integral
format analytically, 55
png, 20 approximately, 55
Fortran, 2 exact, 55
forward difference approximation, 99 numerically, 55
Forward Euler scheme, 101 integration
Fourier series, 51 points, 57
from, 8 interactive use (of Python), 12
function, 7, 31 IPython, 6, 12
asarray, 118
assert, 72 K
call, 7 keyboard
definition, 31 arrow up/down, 12
global, 36
handle, 35 L
input parameter, 7 lambda function, 36
local, 36 language
nested, 36 computer, 2
output parameter, 7 programming, 2
return, 7 Laplace equation, 182
take a parameter, 7 least squares method, 50
legend (plot), 20
G Leibniz
garbage collection, 25 pi, 48
Gauss quadrature, 68 library, 8
Gedit, 6, 212 function, 8
graph, 18 SymPy, 23
linear algebra, 21, 39
H linear interpolation, 49
hardcopy (plot), 20 linspace, 10
heat equation, 161 list, 23, 41
7 Index 223
append, 41 Octave, 2
comprehension, 42 ODE
convert to array, 41 scalar, 116
create, 41 vector, 116
delete, 41 operator
loadtxt, 44 Arithmetic, 13
logistic model Logical, 30
carrying capacity, 107
long lines (splitting of), 25 P
loop package, 8
double, 39 parameter
for, 37 input, 31
index, 37, 40 output, 31
infinite, 40 parentheses, 13
iteration, 37, 40 PDE, 161
multiple, 39 plot, 9, 11
nested, 39 figure, 20
while, 40 Poisson equation, 182
print, 4
M printf formatting, 15
main program, 33 printing
Maple, 2 formatted, 15
math, 8 program
Mathematica, 2, 24 crash, 22
mathematical modeling, 116 execute, 4, 6
MATLAB, 2 flat, 63
matplotlib.pyplot, 10 input, 23
matrix, 21 output, 23
mat, 21 run, 4, 6
tridiagonal, 177 statement, 4
vector product, 21 testing, 22
mesh, 99 typing, 6
points, 99, 164 verification, 22
uniform, 99 programming, 2
method of lines, 163, 164 game, 49
Midpoint method, 65 prompt, 6, 12
model pseudo code, 29
computational, 97 py.test, 72
differential equation, 96 Python, 2
mathematical, 3, 96 documentation, 25
module, 8, 62 installation, 6
MOL, 163 shell, 12
forward Euler, 163 zero-based indexing, 18
Monte Carlo integration, 84
R
N random (function), 30
NameError, 8 random walk, 29
Newton range, 37
starting value, 196 rate of convergence, 70, 201
nonlinear algebraic equation, 132 raw input, 23
nose (testing), 72 read (from file), 43
Notepad++, 6, 212 reserved words, 13
numerical scheme, 101 resonance, 146
numpy, 10 return, 31
None, 200
O value, 33
object, 13 RK2, 131
224 7 Index
§1. Textbooks on topics in the field of computational science and engineering will
be considered. They should be written for courses in CSE education. Both graduate
and undergraduate textbooks will be published in TCSE. Multidisciplinary topics
and multidisciplinary teams of authors are especially welcome.
§2. Format: Only works in English will be considered. For evaluation pur
poses, manuscripts may be submitted in print or electronic form, in the latter case,
preferably as pdf- or zipped ps-files. Authors are requested to use the LaTeX style
files available from Springer at: https://www.springer.com/gp/authors-editors/book
authors-editors/manuscript-preparation/5636 (Click on ! Templates ! LaTeX
! monographs)
Electronic material can be included if appropriate. Please contact the publisher.
§3. Those considering a book which might be suitable for the series are strongly
advised to contact the publisher or the series editors at an early stage.
General Remarks
Careful preparation of manuscripts will help keep production time short and ensure
a satisfactory appearance of the finished book.
The following terms and conditions hold:
Regarding free copies and royalties, the standard terms for Springer mathematics
textbooks hold. Please write to [email protected] for details.
Authors are entitled to purchase further copies of their book and other Springer
books for their personal use, at a discount of 33.3% directly from Springer-Verlag.
Series Editors
2. A. Quarteroni, F. Saleri, P. Gervasio, Scientific Computing with MATLAB and Octave. 4th Edition
3. H. P. Langtangen, Python Scripting for Computational Science. 3rd Edition
4. H. Gardner, G. Manduchi, Design Patterns for e-Science.
For further information on these books please have a look at our mathematics catalogue at the following
URL: www.springer.com/series/5151
1. J. Sundnes, G.T. Lines, X. Cai, B.F. Nielsen, K.-A. Mardal, A. Tveito, Computing the Electrical
Activity in the Heart.
For further information on this book, please have a look at our mathematics catalogue at the following
URL: www.springer.com/series/7417
Lecture Notes
in Computational Science
and Engineering
For further information on these books please have a look at our mathematics catalogue at the following
URL: www.springer.com/series/3527