程序代写案例-MTH6150

欢迎使用51辅导,51作业君孵化低价透明的学长辅导平台,服务保持优质,平均费用压低50%以上! 51fudao.top
MTH6150: Introduction to the LINUX operating system
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary Univer
sity of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
This note provides a basic introduction to the Linux operating system, and the basic we will be
using for the course. The commands that follow work in a Linux or MacOS terminal.
I. CREATING AND DELETING DIRECTORIES
Open a terminal. One of the most important aspects in keeping your files organized is to learn how to create
directories. This can be done with the command
mkdir
Type
mkdir dir1
mkdir dir2
To create directories dir1 and dir2. You can also create in one stroke a directory with subdirectories as follows
mkdir -p dir3/subdir/subsubdir
The -p flag will create not only the directory subsubdir, but also the parent directories subdir and dir3.
To delete an empty directory you can type
rmdir dir1
“rm” for remove, or for multiple directories at once
rmdir dir1 dir2
If the directory “dir1” were not empty rmdir would error out with a message
rmdir: failed to remove ’dir1/’: Directory not empty
To remove a non-empty or empty directory you can type
rm -r dir1
The command “rm” is the general command for removing files or directories, and the flag “-r” is there to tell “rm”
to remove recursively.
II. NAVIGATION
Just like in the Windows operating system, it is important to learn how to navigate through the multiple directories.
• One of the most important commands is “pwd”, which allows you to find your current location. Type
pwd
You should see
/home/netid
where “netid” will be your netid. “pwd” stands for “print working directory”.
2• Another important command is “ls” which lists the contents under your current location. Type
ls
You should see dir3 (assuming you did not delete dir3).
• But, we know that dir3 has subdirectories. How can we go into these directories? This is achieved with the
command “cd”, which stands for “change directory”. Type
cd dir3/subdir/subsubdir
and now type again
pwd
you should see
/home/netid/dir3/subdir/subsubdir
This is the absolute path to the directory “subsubdir”, which you can use to always get into that directory. If
you type
cd
without any argument, you will always end up to your home directory, which you can test by typing “pwd”. If
you type
cd dir3/subdir/subsubdir
This is the relative path to “dir3/subdir/subsubdir” from your home directory.
• You can also use the “..” operator which is used to go one directory up (the immediate parent directory the
working directory), i.e., if you now type
cd ../
and now type
pwd
you should get
/home/netid/dir3/subdir
In other words you are into “subdir”, which is the immediate parent directory of “subsubdir”.
• Now, use the absolute path to the subsubdir to change into “subsubdir”, i.e.,
cd /home/netid/dir3/subdir/subsubdir
and type again
pwd
You should now see “/home/netid/dir3/subdir/subsubdir”. In other words when you use the absolute path, it
does not matter where you are. You will always go into the directory you want.
• One of the most convenient shortcuts of linux is the “tab-completion”. Which helps in writing all these long
paths. Try again changing into the subsubdir using the absolute path
cd /home/netid/dir3/subdir/subsubdir
but each time you hit a letter hit the “tab” button a couple of times, if the path is not auto-completed enter
another letter in the path
3III. FILES
Linux is an extensionless system. It doesn’t depend on the extension of a file to tell what type of file it is. For
example, a pure text file with the extension .txt or .dat or .png or .jpg is treated as a text file in linux.
• Let create our first file using the “touch” command. Type
cd /home/netid/dir3/subdir/subsubdir
and
touch foo.txt foo1.txt foo2.txt
This will create 3 empty files in the subsubdir directory. Strictly speaking, touch just updates the timestamp
of last access of the files. But, since these files never existed they are created by the system.
Lets also write something in these files. Type
echo "First entry into foo file" >> foo.txt
echo "First entry into foo1 file" >> foo1.txt
echo "First entry into foo2 file" >> foo2.txt
Hint: Remember to always use the up arrow to access your previous commands, so that you
don’t have to retype everything.
The “echo” command simply prints the text in the quotes, while the append operator “>>” just appends to
the files the output from echo, i.e., the text in the quotes.
• We can now use the “file” command to figure out the file type, i.e., enter
file foo.txt
and the output should be.
foo.txt: ASCII text
• When choosing file names and directory names, it is more convenient to not use spaces as they require special
treatment. A better practice is to use underscores in the place of spaces, i.e.,
echo "First entry into foo file" >> my_fist_foo_file.txt
• Lets now list the contents of the subsubdir, i.e., type
ls
We can also use options on ls to obtain more information about the files, i.e.,
ls -l
which will list the files in alphabetical order and show the permissions of the files (no need to get into this for
our purposes), the owner and group of the files, the size of the files in bytes (if you use the flags “-lh” it will
print the file size in human readable form), the timestamps for the files and the filenames. We can also use a
combination of options. A useful one is
ls -ltrh
4which lists the files in reverse order time, i.e., the item that was accessed last appears at the bottom of the list.
• Earlier we mentioned that Linux has hidden files. We can list these by using the “-a” or “–all” flag. Go to your
home directory using cd, and type
ls -a
You should now see a number of files that start with a “.”. All these are hidden files. You can also see your
“.bash history” file (which contains your previous commands), as well as one of the most important files in
Linux the “.bashrc”. The “.bashrc” file is loaded every time you log into your linux account and can be used to
define useful macros, including aliases, set shell variables (like the $SHELL variable we used) and other useful
shortcuts.
For most Linux commands there are manual pages which you can access by typing “man” and the command name,
e.g.,
man ls
In the manual pages you can read more about the command and the different options (flags) it has. Of course the
world wide web is always your friend.
A. Moving files around
Copying files or moving them is always useful. Create a new directory in your home directory, typing
mkdir ~/dir1; cd
Notice that we used “∼/”, which is a shortcur to the home directory path. We also used the semicolor “;” operator to
separate the mkdir command from the cd command. You can use multiple semicolons on one line to separate multiple
commands that you can execute in the order they appear and hit enter only once. Now copy the “my fist foo file.txt”
from subsubdir to the dir1 directory. Using the “cp” (copy) command and the paths to the files
cp ./dir3/subdir/subsubdir/my_fist_foo_file.txt dir1/
The first dot “.” is meant to indicate that dir3 is in your working directory. The command would be equivalent to
cp dir3/subdir/subsubdir/my_fist_foo_file.txt dir1/
Remember to always use the tab completion to write the paths, so that you don’t have to type the
full path.
The syntax of the copy command is “cp [source] [destination]”. If you are sure you don’t want to copy the file, but
move it, i.e., no longer have the file in your source you can use the “mv” command, e.g.,
mv ./dir3/subdir/subsubdir/my_fist_foo_file.txt /dir3/subdir/
Now list the contents of the subsubdir directory
ls ./dir3/subdir/subsubdir/
and you should see the file you moved. Also, list the contents of subdir
ls ./dir3/subdir/
and you will see the file was indeed moved. The copy command can be used to copy directories too.
5IV. EDITORS
One of the most important tools in building our programs are text editors. Almost all Linux distributions these
days have vi, vim, emacs and the nano or pico editors. If you know vi or vim or nano or pico feel free to use them.
Here we will see the emacs text editor. Go into the subdir directory and launch emacs
emacs my_fist_foo_file.txt
You can now start to edit this file. Add a few sentences to it.
• To move fast between the text you can use the “pg up” and pg dn” keys, the “home” and “end” keys, you can
keep the “ctrl” key pressed down while using the left, right, up and down arrows to jump between words and
text. To move a page down you can also type
ctrl+v
and to move up
Meta+v
where the “Meta” key is typically the “alt” key (the key left of the space bar).
To move to the beginning of a line you can also type
ctrl+a
and to the end of a line you can also type
ctrl+e
These shortcuts will expedite greatly how quickly you move the cursor through the text. If you want to go
directly to the end of the text (buffer) type
Meta+<
and the beginning of the text (buffer)
Meta+>
• To save what you wrote type
ctrl+x+s
• To close emacs type
ctrl+x+c
• Copying and pasting inside a file is always useful. Open the file with emacs again
emacs my_fist_foo_file.txt
• Move the cursor to the beginning of the text you would like to copy/cut and hit
ctrl+space
• This activates the marking area tool. Use the arrows to highlight the area you want to cut/copy. To cut the
text type
6ctrl+w
• To copy the text type
Meta+w
• To paste the text, move the cursor where you want to paste the text and type
ctrl+y
where “y” stands for yank.
• To remove (kill) an entire line in your text you can type
ctrl+k
• To undo a move type
ctrl+_
In other words
ctrl+shift+-
To redo a move type
ctrl+shift+space bar+-
While these may sound like many commands, keep in mind that practice makes perfect!
V. OTHER WAYS TO READ FILES
Linux has a number of tools to read files
• Cat
cat stands for concatenate. It will print the entire file in one go on your screen. Good for files that are only a
few lines long.
• less
It will print the file in pages that you can browse through using the up/down arrows or the up/down page keys.
Type “Q” to exit.
• head -nA
Here A is a integer number. The command print on screen the first A lines of the file
• tail -nA
Here A is a integer number. The command print the last A lines of the file.
VI. GREPPING
When debugging we will often need to find certain expressions in a file. Using the linux command grep is one of
the best ways to find an expression in a text, e.g.,
grep "First" ~/dir3/subdir/my_fist_foo_file.txt
The syntax is “grep expression file”. The above will search and print all the occurences of “First” in the file
my fist foo file.txt
7VII. MAKEFILES
The Linux utility make provides a convenient way for combining jobs/files that depend on other files. To drive make
we need a Makefile. Most often, the Makefile tells make how to compile and link a program.
A makefile consists of “rules” which have the following structure
target ... : prerequisites ...
recipe
...
A target can be the name of a file. Examples of targets are executable or object files.
The prerequisites are files used as input to generate the target. A target can depend on several files.
A recipe is an action that make executes. Note: you need to put a tab character at the beginning of
every recipe!
Often a recipe is in a rule with prerequisites and will generate a target file if any of the prerequisites
change. This is extremely useful when compiling a very large code that contains multiple files, because using make
will recompile only those files that have changed.
However, a rule with a recipe for a target need not have prerequisites. For example, a rule containing the
rm command associated with a target called “clean” does not have prerequisites.
A simple example Makefile would contain the following
myprogram : main.o utils.o
g++ -o myprogram main.o utils.o
main.o : main.C header.h
g++ -c main.C
utils.o : utils.C
g++ -c utils.C
clean :
rm myprogram main.o utils.o
If we have the files main.C, utils.C and header.h, and run make in the same directory as the makefile, the above
makefile will then build the executable myprogram. The flag “-c” is to just compile files, but not link them. If instead
you type make clean, make will then remove the files myprogram, main.o, utils.o.
In the above makefile, we have 4 targets that include the executable file myprogram, and the object main.o, utils.o
and the target clean. The prerequisites for myprogram are main.o and utils.o which have their own prerequisites
which are main.C, header.h and utils.C. A recipe follows each line that contains a target and prerequisites, that
simply compiles the relevant files.
The target clean is not a file, and all makefiles typically contain it for convenience.
If we have a code that contains many files, it is far more convenient to use Variables to make our Makefiles simpler.
Using variables the previous makefile can be simplified as follows
OBJS = main.o utils.o
cpp=g++
cppflags= -c
myprogram : ${OBJS}
${cpp} -o myprogram ${OBJS}
main.o : main.C myheader.h
${cpp} ${cppflags} main.C
utils.o : utils.C
${cpp} ${cppflags} utils.C
8clean :
rm myprogram ${OBJS}
The variable OBJS above contains the object files our program depends on. The variable cpp is the compiler, and
cppflags is the compiler flags we want to use. Everything else is basically the same and to use a variable we always
put the variable name within curly brackets that follow a dollar sign: ${}. Using variables way we can simply change
the compiler program, the flags, add more flags, such as compiler optimization flags, as well as add/remove more
object files.
A. Compiling multiple programs with one Makefile
We can use one makefile to compile multiple programs. For example lets assume that we have 3 programs. One
program is in files program1.C header.h c utils.C, the second program is contained in file program2.C and the third
in file program3.C. The following Makefile can compile these programs
A simple example Makefile would contain the following
# Target to compile all
all: program1 program2 program3
# Target for program in program1.C, utils.C and header.C.
program1 : program1.o utils.o
g++ -o myprogram1 program1.o utils.o
program1.o : program1.C header.h
g++ -c program1.C
utils.o : utils.C
g++ -c utils.C
# Target for program in program2.C
program2 : program2.o
g++ -o program2 program2.o
program2.o : program2.C
g++ -c program2.C
# Target for program in program3.C
program3 : program3.o
g++ -o program3 program3.o
program3.o : program3.C
g++ -c program3.C
clean :
rm program1 program2 program3 *.o
The above Makefile compiles program1 if you type make program1, program2 if you type make program2, program3
if you type make program3. It will compile all programs if you type make all. Finally, make clean will remove the
executables and all object files.
VIII. BASIC PLOTTING WITH GNUPLOT
Gnuplot is linux utility for plotting data files and functions. Gnuplot has many intrinsic functions one can plot. To
start plotting data or functions first we need to launch gnuplot. To do so type
9gnuplot
Now you will be in the gnuplot command line. To plot the sin(x) simply type
p sin(x)
and hit “enter”. To specify a range in the x axis type
p [-3.1415:3.1415] sin(x)
and hit enter. The last command plots sin(x) from −π to π. You can also set log scales if you desire. For example
type
p [0,10] x**2
and hit enter. The operator “**” is the “to the power of” operator. So, this last command plots the function
f(x) = x2. To set the y-axis to be in log scale (i.e., a log-linear plot) just type
set log y; p [0.1:10] x**2
Here you learned that the semicolon separates different commands. To also set the x-axis in log scale type
set log y; set log x; p [0.1:10] x**2
and hit enter. You should see a straight line.
We can also label the axes
set log y; set log x; set xlabel "x"; set ylabel "f(x)=x*x"; p [0.1:10] x**2
Gnuplot has multiple intrinsic functions log (natural log) log10 (base 10 log), sin(x), , cos(x), tan(x), exp(x)
(the exponential function), . . ..
Gnuplot can make plots not only from analytic functions, but also from data. Put the following data into a file and
save it as “data.txt”.
0.1 0.00316096 0.00537051
0.05 0.00101441 0.00185594
0.025 0.00025341 0.00048454
0.0125 6.3341e-05 0.00012385
0.00625 1.5834e-05 3.1312e-05
As you can see the above data set has 3 columns. To make a plot of column 2 vs column 1, while in the gnuplot
command line simply type
p "data.txt" u 1:2 w l
and hit enter. Here in double quotation marks we place the name of the file “u” stands for “using”, “1:2” stands for
column 2 vs column 1, so the first column that appears goes to the x-axis of the plot. The “w l” stands for “with
lines”, i.e., it connects the data points. If you want to show the points alone use “w p” (with points), and if you want
both points and lines use “w lp” (with line-points).
We can also create a single plot that shows more than one curves, by separating with commas the other curves. We
can use, e.g. the third column, as follows
p "data.txt" u 1:2 w l, "data.txt" u 1:3 w l
The data here are very close to each other, so we need to take log scales, i.e.,
set log x; set log y; p "data.txt" u 1:2 w l, "data.txt" u 1:3 w l
Let’s also add labels to the axes
set log x; set log y; set xlabel "{/Symbol D} x"; set ylabel "Error";
p "data.txt" u 1:2 w l, "data.txt" u 1:3 w l
10
where you can run the first line (i.e., hit enter), and then run the second line (hitting enter again). Here the construct
{/Symbol D} allows you to write Greek letters, the particular one is the upper case delta, i.e., Δ. We can also add
titles to the key (or legend) of the plot, by using the t (title) operator as follows
set log x; set log y; set xlabel "{/Symbol D} x"; set ylabel "Error";
p "data.txt" u 1:2 w l t "Approximation 1", "data.txt" u 1:3 w l t "Approximation 2"
We can also add analytic functions to our plot, e.g.,
set log x; set log y; set xlabel "{/Symbol D} x"; set ylabel "Error";
p "data.txt" u 1:2 w l t "Approximation 1", "data.txt" u 1:3 w l t "Approximation 2", 0.01*sin(x) w l
Notice that the legend is automatically set to the analytic function name.
Once we are happy with how our plot looks, we can output it to a file. To do this we first decide the file type and
set the filename. For example to output a pdf file type
set output "myplot.pdf"
where in double quotes you enter your filename with the corresponding extension. Then you need to set the terminal
to be the corresponding one by typing
set term pdf
and then finally either type
replot
if you have already seen the plot in the gnuplot x-terminal, or to be safe just type the whole command again, i.e.,
set log x; set log y; set xlabel "{/Symbol D} x"; set ylabel "Error";
p "data.txt" u 1:2 w l t "Approximation 1", "data.txt" u 1:3 w l t "Approximation 2", 0.01*sin(x) w l
To exit gnuplot, either hit ctrl+D or type exit and hit enter. If the Greek letter is not rendered properly on the
machine you first have to generate an eps file, and then convert it to pdf. This can be done by using the postscript
terminal of gnuplot. To do this at the step where you set the output file name you type
set output "myplot.eps"
where in double quotes you enter your filename with the corresponding extension. Then you need to set the terminal
to be the corresponding one by typing
set term post eps enhanced color
and finally replot
set log x; set log y; set xlabel "{/Symbol D} x"; set ylabel "Error";
p "data.txt" u 1:2 w l t "Approximation 1", "data.txt" u 1:3 w l t "Approximation 2", 0.01*sin(x) w l
where you can hit enter after the first line, and then continue onto a second line. All different commands can be in
separate lines if you want. Hitting enter after the second line will give the file you want, and you can exit gnuplot.
After generating the myplot.eps file, you can convert it to pdf by using the linux ps2pdf utility, i.e., typing
ps2pdf -dEPSCrop myplot.eps
To view the plot you can use the evince utility, by typing evince myplot.eps or evince myplot.pdf. You can
further convert the pdf plot to png or jpg by using the linux convert (imagemagic) utility. To convert to png run
convert -density 300 myplot.pdf myplot.png
and to convert to jpg run
convert -density 300 myplot.pdf myplot.jpg
MTH6150: Introduction to the C/C++ programming language
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
This note provides a basic introduction to the C/C++ programming language. Classes and object
oriented programming will only be covered briefly, as they are not necessary for numerical analysis.
The notes assume that you are running programs on a Linux terminal, but if you are using an IDE
such as Visual Studio, Xcode or CLion, you may compile and run the code as described in lectures.
I. THE STRUCTURE OF A PROGRAM
The first program for beginners write is a ”Hello World” program, which simply prints ”Hello World” to your
computer screen. Here is such a program
// My first C++ program
#include
int main()
{
std::cout << "Hello World!";
}
Line 1: is a comment. Comments in C/C++ follow after a double slash “//” and has no effect on the program.
Comments are important to include in order to help the readability of a program.
Line 2: Lines beginning with a hash sign “#” are directives, i.e., a set of rules that are read and interpreted
by a program called the preprocessor. In this case, the directive #include , tells the preprocessor to
include a section of standard C++ code, known as header iostream, that allows to perform standard input and
output operations.
Line 3: A blank line. Blank lines have no effect on a program. Just like comments they help the readability of a
program.
Line 4: int main () This line initiates the declaration of a function. Essentially, a function is a group of program
statements which are given a name: in this case, this gives the name ”main” to the group of code statements that
follow. We will discuss functions later.
The main function is a special, in that all C/C++ programs have it because it is the function called when the code
is run.
Lines 5 and 7: The open brace ({) at line 5 indicates the beginning of the main function, and the closing brace
(}) at line 7, its end. Everything between these braces is the function’s body determining what happens when main
is called. All functions use braces to indicate the beginning and end of their definitions. Note that the opening and
closing braces could have been placed at the end of Line 4 and Line 5, respectively, but the code is more readable the
way it is presented. In fact, we could have entered everything in a single line.
Line 6: std::cout << "Hello World!"; This line is a C++ statement. A statement is an expression that
produces an effect. Statements are executed in the same order that they appear within a function’s body.
This statement has three parts: First, std::cout, which stands for the standard character output device (here
this is the computer screen). Second, the insertion operator (<<): what follows after it is inserted into std::cout.
Finally, a text within quotes ("Hello world!"), is what goes into the standard output.
Notice that the statement ends with a semicolon (;). Semicolons mark the end of statements. All C/C++ statements
must end with a semicolon character. One of the most common syntax errors in C/C++ is forgetting to end a statement
with a semicolon.
To compile and run the program first using the linux mkdir create a directory called cpp tutorial. Go into that
directory and follow these steps:
1. Create a file using the emacs editor and call it main.C that contains the program above in it. Save and close the
file.
2. Type
g++ -c main.C
g++ -o myfirstprogram main.o
2The first command will compile the human readable file main.C and produce the object file main.o (in machine
language) without linking.
The second command will generate the executable myfirstprogram by linking any object files we have (which why
it is called the linker). Use ls to ensure these files were created. Obviously you could have named the executable
program myfirstprogram anything you desire.
We could have also compiled in one step as follows
g++ -o myfirstprogram main.C
which produces only the executable. But, the above example separates the compilation process from the linking
process, which is typically important to understand when compiling code that is in multiple files.
3. Run the program by typing
./myfirstprogram
You should see Hello World! on your screen.
Lets now add another print statement in the program
// My second C++ program
#include
int main()
{
std::cout << "Hello World!";
std::cout << "This is my second C++ program!";
}
Compile and run the program. What do you notice? Are the statements printed on two lines?
To make the statements appear on two lines we need to use the end line operator from the stdout library as follows
// My second C++ program
#include
int main()
{
std::cout << "Hello World!" << std::endl; // Prints Hello World!
std::cout << "This is my second C++ program!" << std::endl; // Prints This is my second C++ program!
}
Compile and run the program. What do you notice this time? Notice that above we also added comments to the
code to explain what that line does.
The code could have also been spread into more lines, e.g.,
// My second C++ program
#include
int main()
{
std::cout << "Hello World!"
<< std::endl;
std::cout << "This is my second C++ program!"
<< std::endl;
}
and the result would be exactly the same. Make sure that you comment, indent and spread your codes over lines
to make the code more readable.
3II. IDENTIFIERS
Identifiers are the names of variables we wish to choose. They are sequences of one or more letters, digits, or
underscore characters ( ). Spaces, punctuation marks, and symbols cannot be part of an identifier. It is good practice
to start identifiers with a letter (although they can start with an underscore). Valid identifiers can never start with a
digit, and can never be any of the reserved strings that C++ uses, namely they cannot have names in the following
group
alignas, alignof, and, and_eq, asm, auto, bitand, bitor, bool, break, case, catch, char,
char16_t, char32_t, class, compl, const, constexpr, const_cast, continue, decltype, default,
delete, do, double, dynamic_cast, else, enum, explicit, export, extern, false, float, for, friend,
goto, if, inline, int, long, mutable, namespace, new, noexcept, not, not_eq, nullptr, operator, or,
or_eq, private, protected, public, register, reinterpret_cast, return, short, signed, sizeof, static,
static_assert, static_cast, struct, switch, template, this, thread_local, throw, true, try, typedef,
typeid, typename, union, unsigned, using, virtual, void, volatile, wchar_t, while, xor, xor_eq
Use different identifiers for different variables. Remember that C/C++ is a case sensitive language, thus the variable
identifier Press is not the same as the name press.
III. DATA TYPES
The values of variables are stored somewhere in the computer memory in machine language (zeros and ones). Our
code does not need to know the storage location; it can simply refer to the variable by its identifier. What the code
requires is the kind of data stored in the variable. Storing an integer is not the same as storing a letter/string or a
floating-point number. The C/C++ fundamental data types are
1. Character types: These represent a single character, such as ’C’ or ’&’. The most basic type is char, which is a
one-byte character. There are larger size character types for wider characters, and we will also use strings.
2. Integer types: These can store an integer number value, such as 3 or 15313. These can either be signed or unsigned,
i.e., support or not negative values.
3. Floating-point types: These store real values, such as 3.1415 or 0.03. They have different levels of precision, i.e.,
how many significant digits are accurate.
4. Boolean types: The boolean type, bool type in C++, can be either true or false.
A list of the most common C++ fundamental types is as follows
1. Character types:
• char: 1 byte in size (8 bits)
• char16 t: 16 bits
• char32 t: 32 bits
• wchar t: Represents the largest supported character set.
2. Integer types (by default signed):
• int: 1 byte in size (16 bits), this integer type will be the most common we will use
• long int: 32 bits
• long long int: 64 bits
3. Integer types (unsigned):
• unsigned int: 1 byte in size (16 bits)
• unsigned long int: 32 bits
• unsigned long long int: 64 bits
4. Floating-point types:
4• float: single precision (typically 32 bits)
• double: double precision (typically 64 bits), this type will be the one we will use
• long double: precision no less than double
Increasing the precision makes for slower calculations.
Apart from the above data types we also have void types, which are none of the above (no storage). Note that 16-
bit arithmetic means we can have 216 = 65536 representable values, 32-bit arithmetic can have 232 = 4, 294, 967, 296
representable values, 64-bit arithmetic can have 264 = 18, 446, 744, 073, 709, 551, 616 ∼ 1.8 × 1019 representable
values. For example for signed integers 16-bit arithmetic implies that only the integer numbers in the interval [-
32768,32767] can be represented. On the other 32-bit arithmetic implies that only the integer numbers in the interval
[-2147483648,2147483647] can be represented. For most purposes 32-bit integers will suffice. We will discuss later in
the course how floating point numbers are represented.
IV. DECLARATION OF VARIABLES
C++ requires every variable to be declared with its type before it is used for the first time. This informs the
compiler the size to reserve in memory for the variable and how to interpret its value. Unlike other languages that
require a variable to be declared at the beginning of a program, in C/C++ you can introduce (declare) a variable
anywhere in the program as long as it is declared before it is used for the first time. Here is a simple program of how
we declare the variables.
#include
using namespace std;
int main ()
{
// variable declaration
int a, b;
int c;
// Value assignment:
a = 5;
b = 2;
// Subtraction of b from a
c = a - b;
// Print the result;
cout << "Subtracting " << b << " from " << a << " yields " << c << endl;
/*
Notice that we did not use ‘‘std:cout’’ because
of the statement ‘‘using namespace std;’’ at the beginning. Also
notice that this block is a comment because it is contained within
"slash asterisk asterisk slash". This is a quick way to add multiline
comments to your program without having to type // all the time
*/
// terminate the program:
return 0;
}
Create a file using emacs, type in the above program and compile your file. Then execute the program. What is
the output?
Whenever we declare a variable, it is good practice to initialize the value of that variable. C++ offers multiple
equivalent ways for initializing variables almost at the same place where they are declared. The following program
shows all these ways
5#include
using namespace std;
int main ()
{
// variable declaration and initialization
int a=5, b(3);
int c{0};
// Subtraction of b from a
c = a - b;
// Print the result;
cout << "Subtracting " << b << " from " << a << " yields " << c << endl;
// terminate the program:
return 0;
}
Implement the above program, compile it and execute it. Choose different initialization values for a, b, c and
confirm that the code yields the right outcome.
V. BASIC STRINGS
Strings are not fundamental data types, but are a compound type that has its own class. As such we need to include
the string class in our program if we want to use C++ strings. This is achieved by adding the #include
at the top of the program as in the following simple program
#include
#include
using namespace std;
int main ()
{
string myfirststring;
myfirststring = "This is my first string\n";
cout << mystring;
return 0;
}
Notice the new line “\n” operator at the end of the string. The above program has the same outcome as the
following
#include
#include
using namespace std;
int main ()
{
string myfirststring;
myfirststring = "This is my first string";
cout << myfirststring << endl;
return 0;
}
Strings can be initialized and can change their values after they have been initialized exactly as other data types
can. Strings can also be added (concatenated is the proper verbiage), as in the following program
6#include
#include
using namespace std;
int main ()
{
string myfirststring;
string mysecondstring;
myfirststring = "This is my first string ";
mysecondstring = "and this is my second string";
cout << myfirststring+mysecondstring << endl;
return 0;
}
VI. CONSTANTS
In addition to the previous data types, we can also have constants, i.e., expressions with a fixed value that cannot
be changed. These can be achieved either using the const type or Preprocessor definitions. Here is an example with
const type
#include
using namespace std;
const double PI = 3.14159265359;
const char newline = ’\n’;
int main ()
{
double a=3.0; // radius of disk
double disk;
disk = PI * a * a; // area of disk, the asterisk stands for multiplication
cout << " Disk of radius " << a << " has an area of " << disk;
cout << newline;
}
and an example with preprocessor definitions
#include
using namespace std;
#define PI 3.14159265359
#define NEWLINE ’\n’
int main ()
{
double a=3.0; // radius of disk
double disk;
disk = PI * a * a;
cout << disk;
cout << NEWLINE;
}
We can also increase the precision of out by using the iomanip class as in the following program
#include
7#include
using namespace std;
#define PI 3.14159265359
#define NEWLINE ’\n’
int main ()
{
double a=3.0; // radius of disk
double disk;
disk = PI * a * a;
cout << setprecision(12) << disk;
cout << NEWLINE;
}
VII. BASIC OPERATORS
• Assignment operator (=): a=3 assigns the value of 3 to a. The expression b=a, assigns the value of a to b, but never
the value of b to a.
In C++ you may see an expression like y = 3 + (a = 23);, in which the operation in the parenthesis is performed
first, i.e., the value of 23 is assigned to a, and then the 23 and 3 are added and the result assigned to y, so that y
= 26 in the end.
• Arithmetic operators: + is the addition operator, - the subtraction operator, * the multiplication operator, / the
division operator, and % the modulo operator. )
• Compound assignment assignment operators (+=, -=, /=, *=, %=) are better explained with their equivalent
statements
x+=2 <=> x = x + 2
x-=3 <=> x = x - 3
x*=10 <=> x = 10*x
x%=2 <=> x = x%2
x*=y+1 <=> x = x*(y+1)
• Increment/decrement operators (++,–): the following statements are all equivalent
++x;
x+=1;
x=x+1;
Similarly for the decrement operator.
Exercize: Form groups of 2-4 people and think about the outcome of the following operations. What are the values
of x and y after the operations are completed in either case?
x=10;
y=++x;
and
x=10;
y=x++;
Write a program and compute x and y in both cases. Are your initial expectations met?
8• Comparison operators (==, !=, >, <, >=, <= ):
== equal to
!= not equal to
> greater than
< less than
>= greater than or equal to
<= less than or equal to
• Logical operators (!, &&, || ):
! Boolean NOT
&& Boolean AND
|| Boolean OR
For example the following is false
( (7 == 7) && (1 > 19) )
because the first is true but the second is false. While the following evaluates to true
( (7 == 7) || (1 > 19) )
because of the OR operator, we need only one of the two relations to be true.
• Conditional ternary operator (? ): this has the following structure
condition ? result1 : result2, i.e., if the condition is true then you obtain result1, else you obtain
result2. Look at the following program and figure out its output. Discuss the program with your group and
try to reason the outcome. Then code it up and try it yourself.
#include
using namespace std;
int main ()
{
int x,y,z;
x=2;
y=7;
z = (x>y) ? x : y;
cout << z << ’\n’;
}
• Comma operator (,): The comma operator is used to separate two or more expressions when only one expression
is expected. To see the result implement, compile and execute the following program
#include
using namespace std;
int main ()
{
int a,b;
a = (b=7, b+8);
cout << ‘‘a=’’ << a << ’\n’;
}
9What is the output, i.e., the value of a? Discuss within your group what the comma operator does. What operation
is performed first?
• Precedence of operators: 1) postfix increment / decrement is from left to right, 2) prefix increment / decrement is
from right to left, 3) multiply, divide, modulo are always from left to right, 4) addition, subtraction from left to
right. It is not trivial to remember the precedence of operators especially since C++ has many operators. Always
test your expressions for the precedence of operators, and use parentheses, because expressions in parentheses are
evaluated first.
VIII. INPUT/OUTPUT FROM STANDARD IN/OUT
• Input of numerical values:
We have already seen the cout operator. There exists an input operator cin that reads values from standard input
(which is your keyboard). This is demonstrated with the following example
#include
using namespace std;
int main ()
{
double a;
cout << "Please enter a real number: ";
cin >> a;
cout << "The value you entered is " << a;
cout << " and its square is " << a*a << endl;
return 0;
}
Implement the above program, compile it and execute it. Change the precision of cout, and see how the output is
affected. Do you need to set the precision in both cout operations?
To force scientific notation you can use cout << setprecision(15) << scientific << "The value you entered
is " << a;. Do you need to enter to set scientific in both cout opoerations?
• Input of strings:
The standard header defines a data type called stringstream. stringstream allows the extraction or
insertion operations from/to strings in the same way as they are performed on cin and cout. It is extremely useful
useful for converting strings to numerical values and vice versa. The following code demonstrates this.
#include
#include // We have added
#include // and
using namespace std;
int main ()
{
string mystr;
double length=0,width=0;
double area=0;
cout << "Enter rectangle length in ft: ";
getline (cin,mystr);
stringstream(mystr) >> length;
cout << "Enter rectangle width in ft: ";
getline (cin,mystr);
stringstream(mystr) >> width; // Notice that we reused mystr
cout << "Rectangle area: " << length*width << " ft^2" << endl;
10
return 0;
}
Implement the above program, and write a program that reads also the height and computes the volume of a room.
IX. INPUT/OUTPUT WITH FILES
C++ has 3 classes to perform input/output to/from files:
1. ofstream: To write on files
2. ifstream: To read from files
3. fstream: To both read and write from/to files.
The following program first writes a text and then reads it and prints it on the screen.
#include
#include
#include
using namespace std;
int main () {
ofstream myfl ("mytext.txt"); // We open (i.e. create) the file and name it mytext.txt
if (myfl.is_open()) // This if statement tests if the file exists
{
// Now we write 3 lines to the file
myfl << "Hello my first name is John.\n";
myfl << "My middle name initial is J.\n";
myfl << "My last name initial is Smith\n";
myfl.close(); // Here we close the file
}
else cout << "Unable to open file";
// Now lets read the file we wrote
string line;
ifstream myfile ("mytext.txt");
if (myfile.is_open()) // Test if the file exists
{
while ( getline (myfile,line) ) // the while structure here reads all lines, getline reads these lines
{
cout << line << ’\n’; // We print on screen line by line
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
Implement the previous program. Compile it and run it. Does it produce the file mytext.txt? Does the file have
the expected content? Does the screen output agree with the file content? Use the same structure to introduce six
doubles a1=11, a2=21, a3=31, b1=21, b2=22, b3=33 and output them in two columns to file which has the following
structure
a1 b1
a2 b2
a3 b3
11
Read the file and print it on screen. Discuss within your group how to implement this program, and then each one
of you work independently to implement it. Ensure the screen output is consistent with the file.
X. FLOW CONTROL AND LOOPS
When we write code in any language some of the most important statements are those controling the flow and those
generating loops. We will demonstrate each of these with specific examples.
• Flow control
1. if and else: the syntax is if (condition1){ statement1} else if (condition2){ statement2}... else
{statement n}. The following program demonstrates the use
// if else, testing for positive or negative?
#include
using namespace std;
int main ()
{
int n;
cout << "Please enter an integer number: ";
cin >> n;
if (n>0){
cout << "You entered a positive integer\n";
}
else if (n<0){
cout << "You entered a negative integer\n";
}
else{
cout << "You entered 0\n";
}
}
Implement, compile and execute the previous program. Run the program entering positive, negative and a 0
value to make sure the program works as expected.
2. switch: This is used to check for specific values. The syntax is
switch (expression)
{
case constant1:
group-of-statements-1;
break;
case constant2:
group-of-statements-2;
break;
.
.
.
default:
default-group-of-statements
}
Here is a program demonstrating the use of switch
// switch
#include
using namespace std;
12
int main ()
{
int n;
cout << "Please enter an integer number: ";
cin >> n;
switch (n)
{
case 1:
cout << "You entered the integer 1\n";
break;
case 0:
cout << "You entered zero\n";
break;
default:
cout << "You entered a number I do not understand\n";
}
}
Implement, compile and execute the previous program.
Of course switch in the previous program could have been replaced with if statements as follows
// if else instead of switch
#include
using namespace std;
int main ()
{
int n;
cout << "Please enter an integer number: ";
cin >> n;
if(n==1){
cout << "You entered the integer 1\n";
}
else if (n==0){
cout << "You entered zero\n";
}
else{
cout << "You entered a number I do not understand\n";
}
}
• Loops and flow control
1. The for loop: this iterates over integers. The syntax is for (initialization; condition; increase/decrease)
statement; For example
#include
using namespace std;
int main ()
{
for (int i=1; i<=10; i++) { // Notice that we can declare i in the loop, also ++i would be the same
if (i<10)
{
cout << i << ", ";
}
else
{
13
cout << i << ". ";
}
}
cout << "\n";
}
Exercise: Based on the above program, write a program that also prints the value of i after the loop ends. Does
the program compile? Why do you think it does not? Discuss with your group what the issue may be. Once you
fix the issue and compile the code run it. Is the value of i after the loop 10 or 11?
Now, write a program that uses a for loop to sum the first 10 positive integer numbers, i.e., the sum of numbers
1,2,3,...,10. First compute compute the result analytically, then implement a program that achieves the goal and
compare your program to your analytic answer.
Note that we can have nested loops as in the following program
#include
using namespace std;
int main ()
{
for (int i=1; i<=10; i++) {
for (int j=1; j<=10; j++) {
if (i==j)
{
cout << "i=j= " << i << endl;
}
}
}
}
Jump statements: we will sometimes need to exit loops. This can be achieved with jump statements. In C++,
these are the break and continue statements. The following example codes demonstrate how these work
// break loop example
#include
using namespace std;
int main ()
{
for (int i=1; i<11; i++){
for (int j=1; j<11; j++){
cout << i << ", " << j << endl;
if (i==3)
{
cout << "Can you fight!";
break;
}
}
}
}
The continue statement is demonstrated with the following code
// continue loop example
#include
using namespace std;
int main ()
{
14
for (int i=1; i<10; i++) {
if (i==3){
cout << "\nI am skipping one number\n";
continue;
}
cout << i << ", ";
}
cout << "Done counting!\n";
}
C++ has a goto statement, but I do not encourage its use because it does not make for easily readable code.
2. The while loop: The while loop has simple syntax: while (expression) statement as demonstrated by this
code
// countdown using while
#include
using namespace std;
int main ()
{
int i = 10;
while (i>0) {
cout << i << ", ";
--i;
}
cout << i <<"!\n";
}
Notice what the value of i is after the end of the while statement.
3. The do-while loop: the do while loops has the following syntax do statement while (condition);. It behaves
like a while-loop, but the condition is probed after the execution of statement and not before. The following
simple code demonstrates its use
// echo machine
#include
#include
using namespace std;
int main ()
{
string str;
do {
cout << "Enter something: ";
getline (cin,str);
cout << "You entered: " << str << ’\n’;
} while (str != "Hello");
}
This code will keep asking you to enter something, until you enter “Hello”. Try it out. If you enter “hello” does
the program terminate?
XI. FUNCTIONS
Functions are part of a code that perform a certain task. They help make codes more modular, remove repetitive
pieces of code and make code more transparent and readable. The typical syntax for a function is type name (
argument1, argument2, ...) statements . Some of the function arguments can be input, some can be output
(depending on the function type). But, a function can have pretty much as many arguments as you wish. There are
functions that have a type, e.g., int, float, double, and functions with no type.
15
A. Functions with type
Functions which have a type will return a value. For example, a function of type int, float, or double will return
an integer, float or double value, respectively. Lets look at the following simple example code
// my first function
#include
using namespace std;
int multiplication (int a, int b)
{
int result;
result=a*b;
return result;
}
int main ()
{
int product;
product = multiplication (15,5);
cout << "The product is " << product << endl;
int a=3,b=8;
product = multiplication (a,b);
cout << "The new product is " << product << endl;
}
Notice that a function can be called multiple times and you can also pass variable to it. Also notice that the
variables a, b in the multiplication function are local to that routine alone. They are basically dummy variables, and
completely separate from the variables a,b in the main routine, which are global variables.
Moreover, notice that functions must be declared before the main function. However, they do not have to be
defined before. For example, the previous piece of code can be written as follows
// my first function
#include
using namespace std;
int multiplication (int a, int b);
int main ()
{
int product;
product = multiplication (15,5);
cout << "The product is " << product << endl;
}
int multiplication (int a, int b)
{
int result;
result=a*b;
return result;
}
Compile and execute both programs above. Finally, the functions do not even have to be in the same file. Lets
now learn how to build programs written over different files, and how to compile them.
First, create a file called, e.g. main.C and in it add the following code
// my first code in multiple files
#include
#include "header.h"
16
using namespace std;
int main ()
{
int product;
product = multiplication (15,5);
cout << "The product is " << product << endl;
}
save and close the main.C file. Now create a file called, e.g., functions.C (the file that will contain your functions)
and inside paste the following code
int multiplication (int a, int b)
{
int result;
result=a*b;
return result;
}
save and close the function.C file. Finally, create a file called header.h (this file will contain the declarations of all
your functions) and inside paste the following line
int multiplication (int a, int b);
Note! This could also be
int multiplication (int, int);
So, now your code is in main.C, functions.C and header.h. Place all these files in the same directory. To compile
the code type
g++ -c functions.C main.C
g++ -o function functions.o main.o
This will generate the executable function that you can run and obtain the same result as the previous program.
Try it out!
Exercise: Code a routine for adding two numbers and add to the file functions.C. The declare the routine in your
header.h file and call the routine in the main.C file. Compile the code and run it outputing both the product and the
sum of the two integers a and b.
Now that you know how to compile a code that is spread over multiple files. Go back to the last
section of the introduction to Linux tutorial and read about Makefiles. Follow the instructions in
that section and prepare your own makefile that compiles the code in your main.C, header.h and
functions.C file.
B. Functions without type
There are also functions that have no type, and do not return a value. These are the void functions. An example
is given here
// void function example
#include
using namespace std;
void myvoidfunction ()
{
cout << "This is a void function with no argument!";
}
17
int main ()
{
myvoidfunction ();
}
// void function example2
#include
using namespace std;
void addition (int a, int b, int &c)
{
c=a+b; // This function adds a, b and assigns the result to c.
}
int main ()
{
int a,b,c(0); // c is initialized to 0.
a=3,b=5;
cout << "The value of c before addition is " << c << endl;
addition (a,b,c);
cout << "The new value of c is " << c << endl;
}
In this example you see for the first time that in the definition/declaration of the addition function we have &c
and not c. We will discuss this further below. For now implement the above program and run it. Then define the
routine without the &, i.e., addition (int a, int b, int c) and run the code. Does it work as intended?
Note: You may have noticed that the type of main is int, but in many of our examples we did not actually include a
return statement for main. This is because the compiler assumes the function ends with an implicit return statement:
return 0;.
C. Passing arguments by value and by reference
Variables can be passed by value or by reference. If they are passed by value the value cannot be changed in the
routine. If the value is passed by reference, the value of the variable can change. The following code passes variables
to the function by reference (using the & operator)
// passing variables by reference
#include
using namespace std;
void triple (int& a, int& b, int& c)// The variables are passed by reference
{
a*=3;
b*=3;
c*=3;
}
int main ()
{
int i=1, j=3, k=7;
triple (i, j, k);
cout << "i=" << i << ", j=" << j << ", k=" << k;
}
Had the definition been void triple (int a, int b, int c), the variables would be passed by value and the
cout statement would print the same values as before the call to triple. When passing by value essentially the
18
variables are treated as parameters and we cannot change their values in the function. In these cases we can also pass
the variables with default values, e.g.,
Note: Functions can be of string type, e.g.,
string cat (string& a, string& b)
{
return a+b; // concatenates two strings
}
Add the above function in a program, pass two strings to it, and call it to see the result.
D. Recursive functions
These are functions that can call themselves as in this example
// sum calculator
#include
using namespace std;
int sum (int i)
{
if (i > 1)
return (i + sum (i-1));
}
int main ()
{
int j = 10;
cout << "sum of first " << j << " integers greater than 0 is "<< sum (j);
cout << "\n";
return 0;
}
XII. ARRAYS
Arrays are series of values of the same type placed in chunks of memory that can be individually accessed by adding
an index to a unique identifier. You can think of them as multidimensional mathematical vectors where the index
accesses the vector components. The following example code demonstrates how one declares arrays, initializes them
and accesses their values.
// My first array code
#include
using namespace std;
// Here the size of the array arr1 is 5, and is dictated by the initialization
int arr1 [] = {6, 6, -7, 4, -1};
int n, result=0;
// Notice that the fact we have defined the array arr1, the integers n and result before
// the main routine, implies that they are global variables accessible by
// all functions in our program without having to pass them as arguments to
// functions
int main ()
{
for ( n=0 ; n<5 ; ++n )
{
result += arr1[n]; // notice that arr1[n] accesses the value of the array
19
}
cout << result << endl;
// Below we declare the array arr2 to have size 10
// we do not access the value of the 10th component
int arr2[10];
for (int i=0 ; i<10 ; i++ )
{
arr2[i] = 3*i*i-5*i-2; // notice that here we initialize arr1
}
// Lets sum the components of arr2 this time
result = 0;
for (int i=0 ; i<10 ; i++ )
{
result += arr2[n];
}
cout << result << endl;
return 0;
}
Notice that the first value of the array arr1 is accessed with index 0, i.e., arr1[0] and the last one arr1[4] and
not arr1[5]. If you attempted to access arr1[5] you would try to access an element in the memory that does not
exist.
Implement and run the above program. Discuss within your group why result was set to 0 before summing up
the elements of arr2. What would the code output the second time if we did not set result=0? Test your expectation.
Apart from 1D arrays we can also have multidimensional arrays. For example, the following code shows a 2D arrays,
which you can think of as a matrix
#include
using namespace std;
#include // Library for intrinsic C++ functions like cos, sin, log, pow etc.
#define cols 1000
#define rows 1000
int main ()
{
double mat[rows][cols];
int i,j;
double sum=0;
for (i=0; ifor (j=0; j{
mat[i][j]=(i+1.)*(j+1.);// here we assing values to the 2D array elements
}
// Lets sum up the sin^2 and cos^2 of the matrix elements to the power 4
for (i=0; ifor (j=0; j{
sum+=cos(pow(mat[i][j],4))*cos(pow(mat[i][j],4))+sin(pow(mat[i][j],4))*sin(pow(mat[i][j],4));
}
cout << sum << endl;
}
Here you see that C++ has intrinsic functions, like cos, sin, log, etc. To use this you need the #include
directive. Also you see for the first time the pow function which essentially exponentiates a value. The pow function
20
is computationally demanding and for efficiency it is best to be used for non-integer powers only. Implement and
execute the program above.
Like variables, arrays can be passed as arguments to functions. However, we cannot pass the entire set of values.
What we can pass is the address of the array in the memory (we practically pass pointers which we will discuss below).
The following example demonstrates how we can pass arrays as arguments in functions.
// arrays as arguments in functions
#include
#include
using namespace std;
void sumarray (int arr[], int length) {
int sum=0;
for (int i=0; isum+=arr[i];
}
cout << sum ;
cout << ’\n’;
}
int main ()
{
int n1=10,n2=20;
int arr1[n1], arr2[n2];
for (int i=0;iarr1[i] = i*i;
}
for (int i=0;iarr2[i] = pow(2,i);
}
sumarray (arr1,n1);
sumarray (arr2,n2);
}
In a function declaration, it is also possible to include multidimensional arrays. The format for a 3D array
type name[][size2][size3]
For example, a function with a multidimensional array as argument could be
void myfunction (int threeDarray[][10][20])
Notice that the first brackets [] are empty, while the following ones specify the respective dimension size. This
syntax is necessary. But, we can do better with pointers that we discuss next.
XIII. POINTERS
In a C++ program, you can treat the memory of a computer as a succession of memory cells and each with a unique
address, such that each cell can be located in the memory by use of its unique address. For instance, the cell with
address 2034 is always after cell with address 2033 and before the cell with address 2035, it is also 2000 cells after the
cell with address 34. When we declare a variable in C++, the memory needed to store its value is assigned a specific
address. The quantity that carries the information of what the address is is called a pointer: it points to the address.
21
• Address-of operator (&): The address of a variable can be obtained by use of the address-of operator, which is
invoked by prepending an ampersand symbol (&) to the variable, i.e., addrvar = &var; This operation assigns the
address of variable var to addrvar. Hence, addrvar is not assigned the value of the variable var. Let consider the
following snipet of code
var = 3;
addrvar = &var;
newvar = myvar;
If we assume that the address of var is 2034, then the value of addrvar is 2034, while the value of newvar is 3.
• Dereference operator (*): pointers can be used to access the value of the variable they point to. This is achieved
by the dereference operator (*), which can be read as “value pointed to by”. In our previous example, the value of
*addrvar would be 3.
A. Declaring pointers
A pointer is generally declared as type * name; The following program demonstrates the declaration of pointers
and their use.
// pointers
#include
using namespace std;
int main ()
{
int value1 = 5, value2 = 15;
int * p1, * p2;
p1 = &value1; // p1 = address of value1
p2 = &value2; // p2 = address of value2
*p1 = 10; // value pointed to by p1 = 10, this will also be the value of value1
*p2 = *p1; // value pointed to by p2 = value pointed to by p1, thus 10 is the value of value2
cout << "value1 is " << value << ’\n’;
cout << "value2 is " << value << ’\n’;
p1 = p2; // p1 = p2 (value of pointer is copied), these are the addresses, now p1 points to address of value2
*p1 = 20; // value pointed to by p1 = 20, thus the value of value2 is 20.
cout << "value1 is " << value << ’\n’;
cout << "value2 is " << value << ’\n’;
return 0;
}
The above example demonstrates the use of the address-of and dereference operators. Notice that the values of
pointers can be assigned to other pointers. Discuss the above program with your group. Make sure you understand
it. Note that a declaration of the form int * p1, p2; would mean that p1 is a pointer, but p2 is not a pointer, but
a regular integer.
B. Pointers and arrays
Arrays are related to pointers, work like pointers to their first elements, and, an array can always be implicitly
converted to a pointer of the same type. For instance, consider the following declarations:
int array [3];
int * p;
22
The assignment: p = array; is completely valid. In other words array is a pointer, which represents 3 addresses
in the memory. Lets consider now the following code
// pointers to arrays
#include
using namespace std;
int main ()
{
int array[5];
int * p;
p = array; *p = 10; // Here p points to element 1 of array, then the value pointed to by
// p, i.e., array[0] is set to 10.
p++; *p = 20; // we now increment the address of p, i.e., it takes on the value
// of the address of array[1], hence we set array[1]=20
p = &array[2]; *p = 30; // now p is the address of array[2], thus we set array[2]=30
p = array + 3; *p = 40; // now p is the address of array[0]+3, thus we set array[3]=40
p = array; *(p+4) = 50; // now p is the address of array, thus we set the value pointed by
// the address of (p+4), i.e., array[4]=50
for (int n=0; n<5; n++)
cout << array[n] << ", ";
return 0;
}
Thus, the name of an array is a pointer to the first element of the array.
The main difference between pointers and arrays is that pointers can be assigned new addresses, but arrays cannot.
We previously discussed that square brackets ([]) specify the index of an element of the array. Here we learn that
these brackets are in fact a dereferencing operator known as offset operator. They dereference the variable they follow
just as * does to a pointer, but they also add the number between brackets to the address being dereferenced. For
example:
b[4] = 0; // b [offset of 4] = 0
*(b+4) = 0; // pointed to by (b+4) = 0
The above two expressions are equivalent.
C. Pointers to functions
C++ allows pointers to functions. We use these to pass a function as an argument to another function which makes
for very modular codes. Pointers to functions are declared with the same syntax as a regular function declaration,
except that the name of the function is enclosed between parentheses () and an asterisk (*) is precedes the name. The
following example code shows how this is done
// pointer to functions
#include
using namespace std;
int addition (int a, int b) // addition function
{ return (a+b); }
int subtraction (int a, int b) // subtraction function
{ return (a-b); }
int operation (int x, int y, int (*funct)(int,int)) // general function to which we pass another func.
{
int g;
g = (*funct)(x,y);
return (g);
23
}
int main ()
{
int m,n;
int (*minus)(int,int) = subtraction;// pointer to subtraction
m = operation (8, 3, addition);
n = operation (33, m, minus);
cout <n = operation (33, m, subtraction);
cout <return 0;
}
In the example above, minus is a pointer to a function that has two parameters of type int, and here it is initialized
to point to the function subtraction: int (* minus)(int,int) = subtraction;. Then in the last line we pass the
function subtraction directly to operation.
XIV. DATA STRUCTURES
A data structure consists of data elements grouped together under one name. These data elements, known as
members, can have different types and different lengths. Data structures can be declared in C++ using the following
syntax:
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_name;
The object name is optional. Typically one can define a struct as e.g.
struct product {
int weight;
double price;
}
and then declare data structs as
product kiwi, banana, pineapple;
then each of the kiwi, banana, pineapple struct will have weight and price members that can be accessed using
the “.” operator, i.e.,
kiwi.weight
kiwi.price
banana.weight
banana.price
pineapple.weight
pineapple.price
Data structures are extremely useful in writing modular code, as well as in passing multiple variables as arguments
to a function by using only one name! For example, consider a group of parameters necessary for computing the
gravitational and electric force between two charges: charge1, charge2, mass1, mass2, distance.
24
// using structures
#include
using namespace std;
struct params { // Here we declare the struct
double q1,q2;
double m1,m2;
double dis;
};
void gravitational_force(params sys,double &Fg, double G){
Fg=-G*sys.m1*sys.m2/(sys.dis*sys.dis);
}
void electrostatic_force(params sys,double &Fe, double k){
Fe=k*sys.q1*sys.q2/(sys.dis*sys.dis);
}
int main ()
{
double k=8.85418782e-12; // Coulomb’s law constant in SI
double G=6.674e-11; // Newton’s constant in SI.
params ep; // declaration of params struct for an electron-proton (ep) system
ep.q1=-1.60217662e-19; // e charge in Cb
ep.q2=1.60217662e-19; // proton charge in Cb
ep.m1=9.10938356e-31; // e mass in kg
ep.m2=1.6726219e-27; // p mass in kg
ep.dis=0.01; // 1cm=0.01m
double Fg, Fe;
gravitational_force(ep,Fg,G);
electrostatic_force(ep,Fe,k);
cout << "The ratio of grav. to elec. force between a proton and electron is " << Fg/Fe << endl;
return 0;
}
XV. EXERCISE
Approximating ln(2): We know that ln(2) is the limit of the infinite series:
ln(2) =
∞∑
n=1
(−1)(n+1)
n
=
(
1 +
1
3
+
1
5
+
1
7
+ . . .
)

(
1
2
+
1
4
+
1
6
+ . . .
)
(1)
Write a program that approximates ln(2) to within an accuracy of 1 part in 106. How many terms
in the series do you need to achieve this accuracy?
Here is a flow chart/set of steps:
1. Introduce two estimates for ln2, call them double ln2a, ln2b. Also introduce an integer n. Also introduce a
tolerance variable to check the accuracy of the estimate for ln(2), let’s call it double TOL=1.e-6.
2. Initialize ln2a and ln2b to 0.
25
3. Create a loop over n (can be a while or a for loop), with n starting at value 1. If you choose a for loop you need
to consider the maximum number of iterations in the loop. Notice that the series is a slowly converging one
because each term is order 1/n. Thus, for an accuracy of 1e− 6, you would need more than 106 steps.
4. Inside the loop set (form groups of 2 and reason with your classmates why we are doing the following)
ln2a=ln2a + pow(-1,n+1)/n
ln2b=ln2a + pow(-1,n+2)/(n+1)
Now you have two estimates for the ln(2) from two consecutive terms in the series.
5. Inside the loop check for the accuracy of the estimate. Given that the term with n+1 has a smaller contribution
to the sum than the term n (why is that?), this implies that ln2b is a more accurate estimate for ln(2) than
ln2a. Thus, check for the relative error between these two estimates and exit the loop when the relative error
is smaller than the desired tolerance, e.g.
if(((abs(ln2b-ln2a))/ln2b < TOL){ break; }
Note: To use the C++ abs function you need to include the cmath library # include
6. Outside the loop, output ln2b and the number of terms in the series that you used to achieve the desired
accuracy in calculating ln(2).
How many terms in the expansion do you think you would need to achieve an accuracy of 10−16?
Another representation for ln(2) is the limit of the infinite series:
ln(2) =
∞∑
n=1
1
2nn
(2)
How many terms in the expansion do you need to achieve an accuracy of 1e− 16? How does this compare with the
previous estimator? Why do you think that is?
MTH6150: Roundoff Error
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
This note provides a basic understanding of roundoff error.
I. REPRESENTATION OF REAL NUMBERS ON THE COMPUTER
Real numbers are not fully represented on computers. The IEEE standard for represented 64-bit floating point
numbers (a double in C/C++) is
(−1)s2(c−1023)(1 + f), (1)
where s is called the sign, c the characteristic and f the mantissa. The representation of a real number is
|
sign︷︸︸︷
1bit|
characteristic︷ ︸︸ ︷
11bits |
mantissa︷ ︸︸ ︷
52bits | (2)
Of course s can be 0 or 1. When it is 0 we represent positive real numbers, when it is 1 we represent negative real
numbers. For example, we can have the number (in binary)
|0|10000000011|101110010001
40×︷ ︸︸ ︷
0000000000000000 . . . 0| (3)
which has
s =0
c =1× 210 + 0× 29 + 0× 28 + . . . 1× 21 + 1× 20 = 1027,
f =1×
(
1
2
)1
+ 0×
(
1
2
)2
+ 1×
(
1
2
)3
+ 1×
(
1
2
)4
+ 1×
(
1
2
)5
+ 0×
(
1
2
)6
+ 0×
(
1
2
)7
+ 1×
(
1
2
)8
+ 0 + 0 + 0 + 1×
(
1
2
)12
+ 0 + . . . + 0×
(
1
2
)52
= 0.722900390625
(4)
and hence the real number represented is
N0 = (−1)02(1027−1023)(1 + 0.722900390625) = 27.56640625 (5)
Now, we can start to see the limitations of floating point arithmetic.
The next largest real number that can be represented is
|0|10000000011|101110010001
39×︷ ︸︸ ︷
0000000000000000 . . . 0 1| (6)
This number has the same s, the same c, f which is larger by 1/252 = 2.220446e−16, i.e., f = 0.72290039062500022204,
and hence the number represented is
N1 = (−1)02(1027−1023)(1 + 0.72290039062500022204) = 27.56640625000000355264 (7)
The next smallest real number that can be represented is
|0|10000000011|101110010000
40×︷ ︸︸ ︷
1111111111111111 . . . 1| (8)
This number, let’s call it N−1, has the same s, the same c, and f which is smaller by −1/212 +
∑n=52
n=13 1/2
n = 1/252.
Thus, N0 represents half of the numbers between N0 and N1 and half of the numbers between N−1 and N0. These
examples explictly show that there is a finite number of reals that is represented.
2A. Overflow/Underflow
In addition, there is a finite range of numbers that can be represented. In particular, the largest possible positive
real number corresponds (by convention) to c = 2046, f = 1− 1/252, thus
Nmax = 21023(2− 1/252) ≈ 0.17977× 10309 (9)
The smallest possible positive real number corresponds to c = 1, f = 0, thus
Nmin = 2−1022(1 + 0) ≈ 0.22251× 10−307 (10)
If during a computation a number becomes greater than Nmax, we say we have overflow, and if a number becomes
smaller than Nmin, we say we have underflow.
B. Decimal Machine Numbers
Let the machine numbers be represented in the normalized decimal floating-point form
±0.d1d2 . . . dk × 10n, 1 ≤ d1 ≤ 9, 0 ≤ di ≤ 9, i = 2, . . . k (11)
Numbers of this form are called k-digit decimal machine numbers.
ÄAny positive real number
y = 0.d1d2 . . . dkdk+1dk+2 . . .× 10n (12)
will have a floating-point representation by terminating the mantissa at k decimal digits either by chopping or rounding.
Chopping means remove dkdk+1dk+2 . . . and produces the floating point k-digit number
fl(y) = 0.d1d2 . . . dk . . .× 10n (13)
Rounding adds 5× 10n−(k+1) and chops the result to obtain a number fl(y) = 0.δ1δ2 . . . δk:
• If dk+1 ≥ 5 then dk → dk + 1 (round up).
• If dk+1 < 5 then chop off all but first k digits (round down).
When rounding down δi = di. When rounding up the digits can change.
Exercise: consider π = 3.14159265 = 0.314159265 × 101. What is the number that is represented after 5-digit
rounding or chopping?
Answer: Using 5-digit chopping fl(π) = 0.31415×101 = 3.1415. Using 5-digit rounding fl(π) = chop[(0.314159265+
0.00001)× 101] = chop[(0.314160265)× 101] = 0.31416× 101 = 3.1416.
Definition: If p˜ represents an approximation to a real number p, the relative error
|p− p˜|
p
≡ Roundoff error. (14)
In the previous exersize, what is the roundoff error after chopping? What is the roundoff error after rounding?
Exercise: Think about the way real numbers are represented in a computer, i.e., Eqs. (1)-(5). Based on this, what
is the roundoff error in 64-bit floating point arithmetic?
Answer: The smallest possible change to any real number comes from the mantissa term 1/252 = 2.2204 × 1016.
Thus, this term represents approximately the roundoff error of 64-bit floating point arithmetic, i.e., 64-bit floating
real numbers are accurate to about 16 digits.
MTH6150: Solving non-linear algebraic equations
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
In this note we discuss methods for solving non-linear algebraic equations in one or more variables.
I. SOLVING EQUATIONS IN ONE VARIABLE
In this section we consider one of the most basic mathematical problems: root-finding in one variable. In other
words find the root for an equation of the form f(x) = 0.
A. Bisection
The first method we will consider is called bisection and is based on the Intermediate Value Theorem.
Intermediate value theorem: Let f be a continuous function in the interval [a, b] with f(a)f(b) < 0, in other words
f(a) and f(b) have different opposite signs. Then there exists x0 ∈ (a, b) with f(x0) = 0.
Although bisection works when there multiple roots in the interval (a, b), we assume for simplicity that the root in
this interval is unique. The bisection method then simply repeatedly halves (or bisects) subintervals of [a, b] and, at
each step, locates that half of the subintervale which contains x0 based on the intermediate value theorem.
Flowchart of the algorithm:
• INPUT: endpoints a, b; tolerance TOL; maximum number of iterations N, f(x)
• OUTPUT approximate solution x0 or message of failure.
• Step 1: set fa = f(a), fb = f(b) Check if fa = 0 or fb = 0, and exit saying that you have found roots x = a or
x = b.
If fa 6= 0 and fb 6= 0, then
Check if f(a)f(b) ≥ 0.
If the condition is true exit with a message stating that “if f(x) is continuous, bisection will not be able to find
a root in this interval.”
Else go to Step 2.
• Step 2: Set n = 1; // The counter of the number of iterations
• Step 3: While n ≤ N do Steps 4-7
• Step 4: Set x0 = (a + b)/2; Set fx0 = f(x0);
• Step 5: If fx0 = 0 or (b− a)/b < TOL then
return x0;
• Step 6: Set n+ = 1;
• Step 7: If fa ∗ fx0 > 0, then set a = x0
else set b = x0
• OUTPUT “Could not find a root: Maximum number of iterations reached. Either increase the number of
iterations or check your tolerance or your condition for exiting the loop.”
RETURN
Other stopping procedures can be applied in Step 5. For example you can use the value of the function f(x0) itself,
i.e., stop if fx0 < TOL.
2Another is to stop if |b − a|/b < TOL. Another is to consider two consecutive estimates for x0, lets call them
x0N , x0N−1 and stop when |x0N − x0N−1|/x0N < TOL. The last two stopping conditions while they are very good
because they test for the relative error, they will fail if x0 = 0. In that case you can consider as safety to add a small
number to the denominator, i.e., |b − a|/(b + ²) < TOL and |x0N − x0N−1|/(x0N + ²) < TOL, with ² something on
the order of machine precision, e.g., ² = 1e− 14. In such cases you may need to experiment with the tolerance and ²,
and it is always best to have multiple checks for when you have found a root.
Exercise: Write a code that implements the bisection algorithm and solve x2 + 4x4/3 − 10 = 0, which
has one real root. Think of a convenient interval that brackets the root. Determine the root with an
accuracy of 1 part in 1010. Can you think of a way to determine how many iterations you may need
to achieve such accuracy?
Make sure you introduce a function f(x) = x2 + 4x4/3 − 10. This way your code will be modular, i.e.,
function independent. Therefore, if you want to solve another equation you only need to change the
function. Better, yet, create a file called,e.g., functions.C which contains the bisection function and
the fofx function. Then add a header file and call the bisection function from the main C++ routine.
Note: To exit a C++ program before reaching return you can use exit(0);, for which you will need
#include .
B. Newton-Raphson
One of the most powerful methods for solving non-linear equations in one or more variables is the Newton-Raphson,
which also has a nice geometric interpretation. Let’s derive the algorithm.
Let f be a C2 function in [a, b]. Let xˉ0 be an approximation to a root of f in [a, b]. Since, f is C2 we can Taylor
expand it about xˉ0, i.e.,
f(x) = f(xˉ0) + (x− xˉ0)f ′(xˉ0) + (x− xˉ0)
2
2
f ′′(c), (1)
where the above equation is exact for some c ∈ [a, b], and the primes indicate differentiation. If we evaluate the above
equation at the true root, lets call it x0, then f(x0) = 0 and we obtain
f(xˉ0) + (x0 − xˉ0)f ′(xˉ0) + (x0 − xˉ0)
2
2
f ′′(c) = 0. (2)
Given that we assumed xˉ0 is a good approximation to x0 the term (x0 − xˉ0)2 is much smaller than the first two
terms, so we drop that quadratic term to arrive at
f(xˉ0) + (x0 − xˉ0)f ′(xˉ0) = 0. (3)
which we can solve with x0 – the true root of f , i.e.,
x0 = xˉ0 − f(xˉ0)
f ′(xˉ0)
(4)
This equation is the heart of the Newton-Raphson algorithm as it serves to generate an iterative scheme that
converges to the true root, as follows
xn+1 = xn − f(xn)
f ′(xn)
, n ≥ 0. (5)
Starting with an initial guess x0 for the root, we can otbain a better estimate by means of the above iterative
scheme as
x1 = x0 − f(x0)
f ′(x0)
(6)
3FIG. 1. The geometric interpretation of the Newton-Raphson method for solving equations. Given the guess at x0 we draw
the tangent to f(x) at x0. The next guess for the root (x1) is where tangent1 intersects the x-axis. Then we draw the tangent
to f(x) at x1, and the next guess for the root (x2) is where tangent2 intersects the x-axis. Next we draw the tangent to f(x)
at x2, and the next guess for the root (x3) is where tangent3 intersects the x-axis. Next we draw the tangent to f(x) at x3,
and the next guess for the root (x4) is where tangent4 intersects the x-axis, which is very close to the true root of f(x).
Then x1 can be used to get an even better estimate
x2 = x1 − f(x1)
f ′(x1)
(7)
And continue until the scheme converges. Given two consecutive approximations to the root, we have converged
when |xn+1 − xn|/|xn+1| < TOL. The other criteria for convergence we discussed for bisection can be applied here,
too.
Notice that one of the greatest pitfalls of the Newton-Raphson method is when the derivative of the function
becomes 0. When deriving Eq. (4) we implicitly assumed that f ′(xˉ0) 6= 0. If during the iteration the derivative
becomes 0, then obviously dividing by 0 will be a bad thing. Another pitfall of the algorithm is that if the function
has multiple roots, then during the iteration the algorithm may leave the domain of the root we are looking for.
Usually, changing the initial guess helps overcome these problems, but it is not always guaranteed.
The algorithm has a nice geometric interpretation. Essentially at each guess for the solution we approximate the
function by its tangent at the guess point, and the point where the tangent intersects the x axis becomes the new
guess. This is demonstrated in Fig. 1.
A set of steps for implementing the Newton-Raphson algorithm are as follows:
INPUT initial approximation xˉ0 ; tolerance TOL; maximum number of iterations N .
OUTPUT approximate solution x0 or message of failure.
• Step 1: Set n = 1.
• Step 2: While n ≤ N do Steps 3-6.
• Step 3: Set x = x0 − f(x0)/f ′(x0) (Compute xn+1)
• Step 4: If |x− x0|/|x| < TOL then return x; (The procedure was successful.)
• Step 5: Set n+ = 1. (Next iteration);
• Step 6: Set x0 = x (Update x0);
• Step 7: OUTPUT (“The method failed after N iterations”);
(The procedure was unsuccessful.) EXIT.
C. Secant method
A weekness of the Newton-Raphson method is that we need to know the derivative of f(x) at each iteration. For
non-linear equations computing the derivative analytically can be difficult or it can be a very long expression that
requires many algebraic computations that render the algorithm slow. To overcome this issue the computation of the
derivative can be performed numerically using its definition, i.e.,
f ′(x0) = lim
x→x0
f(x)− f(x0)
x− x0 . (8)
If we assume that x and x0 are sufficiently close then we can approximate
f ′(x0) ≈ f(x)− f(x0)
x− x0 . (9)
In terms of the iterative Newton-Raphson scheme that needs the derivative, this can be written as
4f ′(xn) ≈ f(xn)− f(xn−1)
xn − xn−1 . (10)
If we plug in Eq. (10) in Eq. (5), we obtain
xn+1 = xn − f(xn)(xn − xn−1)
f(xn)− f(xn−1) , n ≥ 0. (11)
Equation (11) is the essense of the secant method. A set of steps for implementing the secant algorithm are as
follows:
INPUT initial approximations x0, x1 ; tolerance TOL; maximum number of iterations N .
OUTPUT approximate solution x1 or message of failure.
• Step 1: Set n = 2. fx0 = f(x0), fx1 = f(x1)
• Step 2: While n ≤ N do Steps 3-6.
• Step 3: Set x = x1 − fx1(x1 − x0)/(fx1 − fx0) (Compute xn+1)
• Step 4: If |x− x1|/|x| < TOL then return x; (The procedure was successful.)
EXIT.
• Step 5: Set n+ = 1. (Next iteration);
• Step 6: Set x0 = x1, fx0 = fx1, x1 = x, fx1 = f(x) (Update x0, fx0, x1, fx1);
• Step 7: OUTPUT (“The method failed after N iterations”);
(The procedure was unsuccessful.) EXIT.
Note: It is also possible to create hybrid methods. For example bisection can be combined with the secant method
to accelerate convergence. The resulting method is called Brent’s method, but we will not be going into greater details
during this course. We only mention this here for the interested reader.
II. SOLVING SYSTEMS OF ALGEBRAIC EQUATIONS
So far we discussed methods for solving non-linear equations in one variable. However, in Physical sciences we often
face the challenge of having to solve a system of N non-linear algebraic equations for N unknown variables. Here we
will discuss an extension of the Newton-Raphson method for systems of equations.
A. Newton-Raphson method for systems of non-linear equations
Lets consider a system of N equations fi with N unknown variables (xi)
f1(x1, x2, x3, . . . , xN ) =0
f2(x1, x2, x3, . . . , xN ) =0
...
fN (x1, x2, x3, . . . , xN ) =0
(12)
We can also introduce the vector of all equations, i.e.,
F =

f1
f2
...
fN
 (13)
5We can now follow the same path as in the Newton-Raphson method for one single variable, i.e., use Taylor series.
Only here we need to use multivariate calculus, to perform a first-order Taylor series for F about x = x0, i.e.,
fi(x) = fi(x0) +
N∑
j=1
∂fi
∂xj
(xj − x0), i = 1, . . . N, (14)
where x = (x1, x2, . . . xN ) are the true roots of fi, and x0 = (x1,0, x2,0, . . . xN,0) are the approximate roots. Since x
are the exact roots, we have fi(x) = 0. Thus, the previous equation becomes
N∑
j=1
∂fi
∂xj
(xj − x0) = −fi(x0), i = 1, . . . N, (15)
Note that ∂fi∂xj is evaluated at x0 and is the i, j element of the Jacobian J matrix of the system of equations (20). This
is easier to see if we write this last equation in the following matrix format

∂f1
∂x1
∂f1
∂x2
. . . ∂f1∂xN
∂f2
∂x1
∂f2
∂x2
. . . ∂f2∂xN
...
∂fN
∂x1
∂fN
∂x2
. . . ∂fN∂xN


x1 − x1,0
x2 − x2,0
...
xN − xN,0
 = −

f1(x0)
f2(x0)
...
fN (x0)
 (16)
or the following shorter matrix notation
J ∙ (x− x0) = −F (x0). (17)
The formal solution of this last equation is
x = x0 − J−1F (x0), (18)
where J−1 is the inverse of the Jacobian evaluated at x0 and F the column vector of equations we introduced earlier.
As in the original Newton-Raphson method, this last equation forms the basis of the following iterative scheme
xn+1 = xn − J−1(xn)F (xn), (19)
i.e., starting from an initial guess we compute the vector of variables xn, the residual of equations F (xn), and the
Jacobian J(xn), we then invert the Jacobian and use Eq. (19) to obtain the new approximation to the solution.
Systems of 2 equations with 2 unknowns: Lets apply the above to the simple case of a 2 × 2, system.
f1(x1, x2) =0
f2(x1, x2) =0
(20)
With the vector of all equations, i.e.,
F =
(
f1
f2
)
(21)
is easier to see if we write this last equation in the following matrix format
The Newton-Raphson method
xn+1 = xn − J−1(xn)F (xn), (22)
in the 2× 2 case translates to the following matrix equations
(
x1,n+1
x2,n+1
)
=
(
x1,n
x2,n
)

(
∂f1
∂x1
|x1,n,x2,n ∂f1∂x2 |x1,n,x2,n
∂f2
∂x1
|x1,n,x2,n ∂f2∂x2 |x1,n,x2,n
)−1(
f1(x1,n, x2,n)
f2(x1,n, x2,n)
)
(23)
6Notice that the 2× 2 matrix above is the inverse of the matrix shown. Recall that the inverse of a 2 × 2
matrix is given by (
a b
c d
)−1
=
1
ad− cb
(
d −b
−c a
)
(24)
Note: the Newton-Raphson method for systems of equations has similar pitfalls as the original method for an
equation in one variable. Namely, if the inverse of the Jacobian does not exist the scheme will fail. From linear
algebra you know that if the determinant of a matrix is 0, then it cannot be inverted. Thus, before using the Newton-
Raphson method for systems of equations one first checks whether the determinant of the Jacobian matrix is 0 or
not.
A set of steps for implementing the Newton-Raphson algorithm for a set of equation are as follows:
INPUT initial approximation to all variables xˉ0 ; tolerance TOL; maximum number of iterations N .
OUTPUT approximate solution x0 or message of failure.
• Step 1: Set n = 1.
• Step 2: While n ≤ N do Steps 3-7.
• Step 3: Compute the determinant of the Jacobian matrix of the system for x0. If the determinant is 0 exit
stating that the Jacobian cannot be inverted. Else, continue to Step 4.
• Step 4: Compute inverse of Jacobian. Set x = x0 − J−1(x0)F (x0) (Compute the vector xn+1)
• Step 5: If |x−x0|/|x| < TOL (for each of the unknown variables separately) then return x; (The procedure was
successful.)
EXIT.
• Step 5: Set n+ = 1. (Next iteration);
• Step 6: Set x0 = x (Update x0);
• Step 7: OUTPUT (“The method failed after N iterations”);
(The procedure was unsuccessful.) EXIT.
MTH6150: Interpolation
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
In this note we discuss methods for performing interpolation in one or more variables.
I. INTERPOLATION IN ONE VARIABLE
In many occations we do not have analytic functions to work with, but we have only numerical data, and we need
the value of a function at a point where we do not have data. This is shown in Fig. 1. The procedure for “predicting”
f(x0) is called interpolation if x0 is within the range of x values for which we have data. If x0 is outside the range of
x values for which we have data, then the procedure is called extrapolation and should be avoided.
An important theorem on which interpolation is based on is the following:
Weierstrass Approximation Theorem: Let f(x) be defined and continuous for x ∈ [a, b]. For each ² > 0, there exists
a polynomial P (x) such that |f(x)− P (x)| < ², ∀ x ∈ [a, b] .
In other words any continuous function can be approximated by a suitable polynomial. You may think that we can
always achieve this with Taylor polynomials. Lets consider ex, for which the Taylor polynomials at x = 0 are
P0(x) = 1, P1(x) = 1 + x, P2(x) = 1 + x +
x2
2
, P3(x) = 1 + x +
x2
2
+
x3
6
, . . . (1)
As shown in Fig. 2, these polynomials converge to the correct function as the order of the polynomial increases.
However, this is not the case for all functions. Lets consider f(x) = 1/x for which the Taylor series at x = 1 is
Pn(x) =
n∑
k=0
(−1)k(x− 1)k (2)
As shown in Fig. 3, these polynomials do not converge to the correct function as the order of the polynomial
increases. More specifically, for x & 2 the polynomials diverge!
x0
0.5 1.0 1.5 2.0 2.5 3.0
x
0.2
0.4
0.6
0.8
1.0
f(x)
FIG. 1. The function f(x) shown here has only numerical data (blue points) and we do not know its analytic form. However,
we would like to find the value of f at x = x0 (red point). The procedure for “predicting” f(x0) is called interpolation if x0 is
within the range of x values for which we have data as is the case with the red point shown here.
2exp(x)
1
1+ x
1+ x+ x
2
2
1+ x+ x
2
2
+ x
3
6
1+ x+ x
2
2
+ x
3
6
+ x
4
24
0.2 0.4 0.6 0.8 1.0 1.2 1.4
x
1.5
2.0
2.5
3.0
3.5
4.0
4.5
f(x)
FIG. 2. The function f(x) = ex and its first 5 Taylor polynomials. Notice that as the order of the polynomial increases, we
obtain a better and better approximation to f(x) = ex, i.e., we converge to the correct values.
1
x
1
2-x
2+(x-1)2-x
2+(x-1)2-(x-1)3-x
2+(x-1)2-(x-1)3+(x-1)4-x
1 2 3 4
x
-2
2
4
6
f(x)
FIG. 3. The function f(x) = 1/x and its first 5 Taylor polynomials at x = 1. Notice that as the order of the polynomial
increases, in no sense do the polynomials converge to f(x) = 1/x, e.g., at x = 3.
The reason why Taylor polynomials do not always work is because these are very local. Once we move away from
the expansion point, we can easily lose convergence. To correctly find interpolating values, we need to use more global
infomation. This is what is achieved by the Lagrange Interpolating Polynomials that we discuss next.
A. Lagrange Interpolating Polynomials
Lagrange polynomials agree exactly with a function f(x) at N distinct points. For simplicity lets consider two
points: (x0, y0) and (x1, y1), with yi = f(xi).
Let
3(xk,1)
x0 x1 x2 . . . . . . xk-1 xk+1 . . . . . . . . xn
x
-0.2
0.2
0.4
0.6
0.8
1.0
Ln,k(x)
FIG. 4. The function Ln,k(x) which is equal to zero at all xi, and equal to 1 at xi = xk.
L0(x) =
x− x1
x0 − x1 , L1(x) =
x− x0
x1 − x0 . (3)
The linear Lagrange interpolating polynomial through (x0, y0) and (xy, y1) is defined as
P (x) = L0(x)f(x0) + L1(x)f(x1) =
x− x1
x0 − x1 f(x0) +
x− x0
x1 − x0 f(x1) (4)
Note that L0(x0) = 1, L0(x1) = 0, L1(x0) = 0, L1(x1) = 1, which imply P (x0) = f(x0) and P (x1) = f(x1).
Most importantly P is unique.
Generalization to degree n: For any polynomial of the form Pn(x) =
∑n
i=0 aix
i, there are (n+1) ai coefficients.
Thus, to completely determine all coefficients we need n+1 conditions. For the Lagrange polynomial these conditions
are Pn(xk) = f(xk), k = 0, 1, . . . n. Thus, a degree n Lagrange polynomial requires n + 1 points at which we know
the values of a function. Consider that we have the set of points
(x0, f(x0)), (x1, f(x1)), . . . (xn, f(xn)). (5)
To construct the degree n Lagrange polynomial, first ∀ k = 0, 1, . . . n construct a function Ln,k(x) such that Ln,k(xi) =
0, i 6= k. To satisfy Ln,k(xi) = 0, i 6= k, the function Ln,k(x) must be
Ln,k(x) ∝ (x− x0)(x− x1) . . . (x− xk−1)(x− xk+1) . . . (x− xn) (6)
We also want Ln,k(xk) = 1. Given Eq. (6), this can be achieved if we divide Eq.(6) by (xk − x0)(xk − x1) . . . (xk −
xk−1)(xk − xk+1) . . . (xk − xn). Then, we arrive at the final form
Ln,k(x) =
(x− x0)(x− x1) . . . (x− xk−1)(x− xk+1) . . . (x− xn)
(xk − x0)(xk − x1) . . . (xk − xk−1)(xk − xk+1) . . . (xk − xn) =
n∏
i=0,i 6=k
x− xi
xk − xi . (7)
A graphical illustration of this function is shown in Fig. 4, where you can see that Ln,k(xi) = 0, i 6= k, and
Ln,k(xk) = 1.
Once we have constructed the function Ln,k(x) then the Lagrange interpolant of degree n is given by
Pn(x) =
k=n∑
k=0
Ln,k(x)f(xk) (8)
4Exercise 1: Consider the function f(x) = 1/x, its 2nd-order Taylor expansion at x0 = 1 (T2(x)), and the quadratic
Lagrange interpolant (P2(x)) using x0 = 1 and x1 = 2, and x2 = 3. Compare the value f(1.5) with T2(1.5) and with
P2(1.5). Is T2(1.5) or P2(1.5) a better approximation to f(1.5)?
Error theorem: Let n + 1 distinct values x0, x1, . . . , xn ∈ [a, b], and f ∈ Cn+1[a, b]. Then for each x ∈ [a, b] there
exists a number c ∈ (a, b) and between xi, such that
f(x) = Pn(x) +
f (n+1)(c)
(n + 1)!
(x− x0)(x− x1) . . . (x− xn), (9)
where Pn(x) is the Lagrange interpolating polynomial and f (n+1) the n + 1-th derivative of f . The second term on
the right-hand-side of Eq. (9) is the error of the interpolation.
Thus, the max. error in [a, b] will be given by
max
[∣∣∣∣f (n+1)(c)(n + 1)!
n∏
i=0
(x− xi)
∣∣∣∣]. (10)
This expression can be used to determine the tolerance within which we desire the interpolation. Note that if the
analytic form of the function is not known, the above expression cannot be applied. This is the case when only
numerical data are provided. In such case, one can estimate an error for Pn(x) in [a, b] by computing Pn+1(x) in [a, b]
and then in [a, b] computing
max
[∣∣∣∣Pn+1(x)− Pn(x)∣∣∣∣]. (11)
Exercise 2:a) Consider f(x) = 1/x given in [2, 4]. Write a code that splits the interval [a, b] = [2, 4] in n
equidistant intervals, i.e, n + 1 points, where you will define f(xi). This can be achieved if you define the grid points
xi = a+ i∗(b−a)/n, i = 0, 1, 2, . . . n. Thus, the grid consists of n+1 grid points. What is the highest degree Lagrange
interpolant you can construct with n + 1 points? Follow these steps:
• Declare an array (xi) of size n + 1, construct a loop and save the coordinate points xi into the array xi.
• Declare an array (yi) of size n + 1, construct a loop and save the value f(xi) at the coordinate points xi into
the array yi. For convenience introduce a function f(x) = 1/x to do these computations. This way your code
will be modular as you can change f(x) if you wish to.
• Construct the unique n-th degree Lagrange polynomial given xi, yi and use it to interpolate at x = 3.127 for
various values of n. It may be convenient to construct a C++ function Ln,k(x) which takes n, k, xi, x as
arguments and returns the value of Ln,k(x). If you have the function Ln,k(x), you can easily contruct the
Lagrange interpolating polynomial through Eq. (8), which will be a C++ function that takes xi, yi, x as
inputs calls Ln,k(x) and uses Eq. (8) to output Pn(x).
• Run your code for n = 3, 5, 9, 17 and output the Degree of the polynomial and the relative error |Pn(3.127) −
f(3.127)|/|f(3.127)|?
Does the relative error |Pn(3.127) − f(3.127)|/|f(3.127)| decrease as we increase the degree of the Lagrange poly-
nomial? Make a plot of the relative error vs the degree of the Lagrange polynomial and determine the trend. Is the
trend a power law, exponential, something else? Think about what kind of plot you need to make: log-linear, log-log
to determine whether the trend is a power law or exponential.
Note: when interpolating a function that is known at N + 1 points, we don’t have to introduce an
Lagrange interpolant that is N-th order. We can always use a smaller number of points, say 3 or 4
(or whatever number you desire) that are the nearest ones to and contain the point at which we want
to interpolate the function, and then perform 2nd, 3rd or other degree Lagrange interpolation. This
saves computational cost and memory. The disadvantage of this approach is that the interpolating functions in
general will not match the slope of the function being interpolated, in other words the interpolants at different points
will not match smoothly one onto another. This issue can be resolved by spline interpolation that we will not cover
here, but you can look up online or in numerical methods books.
5B. Introduction to Spectral Interpolation
Lets assume we have a complete basis functions bn(x). Then any smooth function f can be expanded as
f(x) =
∞∑
n=0
anbn(x) (12)
For periodic functions a Fourier basis is always the preferred choice. In cases where there is no periodicity and
f is highly smooth (especially when it is C∞), the preferred basis is formed by the Chebyshev polynomials. These
polynomials are defined for |x| ≤ 1, and are given by
Tn(x) = cos(n arccos(x)) or Tn(cos(x)) = cos(nx) (13)
Thus, an N -th order approximation to f ∈ [a, b] is
f(x) =
N∑
n=0
anTn(x), (14)
but first we need a map from [a, b] to [−1, 1] where the Chebyshev polynomials are defined. The simplest possible
map is a linear one, i.e.,
x = Au + B (15)
and to determine the A,B coeficients for the new variable u ∈ [−1, 1] we require x = a, u = −1, x = b, u = 1 which
yields the following system of equations
a = −A + B, b = A + B (16)
whose solution gives
A =
b− a
2
, B =
a + b
2
. (17)
Thus, the map is
x =
b− a
2
u +
a + b
2
, (18)
or
u =
2x− a + b
b− a (19)
Thus, for every x ∈ [a, b] there exists a unique u ∈ [−1, 1], so we can now write
f(x(u)) =
N∑
n=0
anTn(u), (20)
How do we compute the coefficients an? There is a standard procedure using:
• the so-called Gauss-Lobatto collocation points, which are the zeroes of (1 − u2)T ′k(u), and are given by
un = cos(
πn
k
), n = 0, 1, . . . , k (21)
• the Chebushev orthogonality property
(Tk, T`)W =
∫ 1
−1
TkT`wdx =
π
2
ckδk`, (22)
where δk` is the Kronecker delta, ck = 2 for k = 0 while ck = 1 for k ≥ 1, and the weightfunction w = (1−x2)−1/2.
The quantity (Tk, T`)W is a weighted inner product in the space of integrable functions.
6Then using Eq. (20) we can compute the inner product of f with a Chebyshev polynomial as
(f, T`)w =
∫ 1
−1

n
anTnT`wdx =

n
an
∫ 1
−1
TnT`wdx =
π
2

n
ancnδn` =
π
2
a`c`. (23)
This last expression serves to determine the coefficient a`, through
a` =
2
πc`
(f, T`)w. (24)
Therefore, to detemine the expansion coefficients, we need to compute the integral 2πc` (f, T`)w. This is performed by
using Gaussian quadratures that we will discuss later in the course. For now, we can just give the straight answer
that for any function p(x) we have ∫ 1
−1
p(u)wdx ' π
N
N∑
i=0
p(ui)
cˉi
, (25)
where
cˉk = 2, if k = 0, cˉk = 1, if 1 ≤ k ≤ N − 1, cˉk = 2, if k = N, (26)
and where again the Gauss-Lobatto points are
ui = cos(
πi
N
), i = 0, 1, . . . N. (27)
Thus, to summarize a function can be approximated as
f(x(u)) =
N∑
`=0
a`T`(u), (28)
with
a` =
2
πc`
∫ 1
−1
fT`wdx ' 2
Nc`
N∑
i=0
f(x(ui))T`(ui)
cˉi
. (29)
If f(x) is a polynomial, the above expression is exact! The above procedure is called Spectral interpolation and if
f is C∞ the expansion is exponentially convergent, i.e., the relative error converges to 0 exponentially.
MTH6150: Differentiation
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
In this note we discuss methods for computing numerical derivatives.
I. NUMERICAL DIFFERENTIATION
In many occasions we do not have analytic functions to work with (for example when solving differential equations),
but we need to compute the derivative of a function with respect to a variable given the values of the function at a
finite number of points. Computing derivatives of functions known at a finite number of grid points is called numerical
differentiation. There are multiple ways one can compute a numerical derivative and we will review here some of these
methods.
A. Finite differences: first order derivatives
We have already seen a numerical approximation to the derivative of a function f(x) at a point x in the secant
method for solving non-linear equations. We used the definition of the derivative to achieve this, i.e.,
f ′(x0) = lim
x→x0
f(x)− f(x0)
x− x0 . (1)
If we define Δx = x− x0, the above is also written as
f ′(x0) = lim
Δx→0
f(x0 + Δx)− f(x0)
Δx
, (2)
which gives us the following numerical approximation
f ′(x0) ≈ f(x0 + Δx)− f(x0)Δx . (3)
This approximation does not allow us to understand the level of error we are making (or how quickly the approximation
converges to the correct value as the difference Δx = x − x0 goes to 0), when we compute the derivative with this
simple formula. To understand this we need to find more accurate ways for computing the numerical derivative.
To simplify the analysis in this subsection we will assume that the function f(x) is known on a grid of points with
fixed grid spacing Δx. In other words, we will assume that we know the function at points xi = x0 + i ∙Δx, where
x0 is the point we want to compute the derivative at, and i is an integer. Next we will Taylor expand the function at
x0 + Δx, i.e.,
f(x0 + Δx) = f(x0) + f ′(x0)Δx +
1
2
f ′′(x0)Δx2 +
1
3!
f ′′′(x0)Δx3 + . . . , (4)
where the ellipsis correspond to the higher order terms which we will neglect for now. We can now use this last
equation to form the right-hand-side of Eq. (3), which gives
f(x0 + Δx)− f(x0)
Δx
= f ′(x0) +
1
2
f ′′(x0)Δx +
1
3!
f ′′′(x0)Δx2 + . . . (5)
and solving with respect to (wrt) f ′(x0) we obtain
f ′(x0) =
f(x0 + Δx)− f(x0)
Δx
−1
2
f ′′(x0)Δx︸ ︷︷ ︸
leading order truncation error
− 1
3!
f ′′′(x0)Δx2 + . . . . (6)
Assuming Δx is sufficiently small, the term proportional to Δx2 is small compared to the term proportional to Δx,
thus the leading order truncation error is (1/2)f ′′(x0)Δx (it is called truncation error because we truncate the series).
2In other words the error is first order in Δx which we write as O(Δx). Thus, the approximation of Eq. (3) is only
first order accurate
f ′(x0) =
f(x0 + Δx)− f(x0)
Δx
+ O(Δx), (7)
which that as we decrease Δx the numerical derivative approaches the correct value at a rate proportional to Δx.
In most applications, the first-order approximation is not good enough to obtain a good approximation to numerical
derivatives. More accurate expressions are desireable, especially those that converge to the correct answer at a faster
rate that first order. To achieve this we need to use either more information or choose the information in a smarter
way. Notice that the expression Eq. (3) uses the values of f(x) at two grid points, i.e., f(x0) and f(x0 + Δx). To
derive a second-order accurate expression for f ′(x0) in fact we do not need more than 2 grid points as we will show
now.
Let us consider again the Taylor expansion of f(x0 + Δx),
f(x0 + Δx) = f(x0) + f ′(x0)Δx +
1
2
f ′′(x0)Δx2 +
1
3!
f ′′′(x0)Δx3 + . . . (8)
but also consider the Taylor expansion of f(x0 −Δx),
f(x0 −Δx) = f(x0)− f ′(x0)Δx + 12f
′′(x0)Δx2 − 13!f
′′′(x0)Δx3 + . . . (9)
We can now subtract Eq. (9) from Eq. (8) to obtain
f(x0 + Δx)− f(x0 −Δx) = 2f ′(x0)Δx + 13f
′′′(x0)Δx3 + . . . (10)
Solving the last equation for f ′(x0) we obtain
f ′(x0) =
f(x0 + Δx)− f(x0 −Δx)
2Δx
− 1
6
f ′′′(x0)Δx2 + . . . . (11)
Notice now that if we approximate the derivative as
f ′(x0) ≈ f(x0 + Δx)− f(x0 −Δx)2Δx (12)
The error is O(Δx2). Notice also we are again using 2 grid points of information, but now the derivative is more
accurate as the leading order error term decreases quadratically with Δx!
The numerical derivative in Eq. (12) is known as a second-order accurate centered finite difference (it uses equal
number of points to the left and right of x0), while the expression (3) is known as a first-order accurate forward
difference approximation (it uses one point to the right of x0 and no points to the left of x0). Using Eq. (9) we can
also derive the first-order accurate backward difference approximation, i.e.,
f ′(x0) ≈ f(x0)− f(x0 −Δx)Δx . (13)
For many practical purposes using second-order accurate expressions suffice for a decent approximation to the first-
order derivative. However, if we have a function in an interval [a, b], and we need the derivative at x = a or x = b
we cannot use the centered finite difference approximation since there is no point left of x = a and no point right of
x = b. At x = a we need a forward finite difference approximation, and at x = b we need a backward finite difference.
Can we obtain second-order accurate forward/backward difference approximations? The answer is yes,
but it comes a price. Let us see how this can be achieved for the forward difference. Consider the Taylor expansion
Eq. (8) multiplied by 4, i.e.
4f(x0 + Δx) = 4f(x0) + 4f ′(x0)Δx + 2f ′′(x0)Δx2 +
2
3
f ′′′(x0)Δx3 + . . . (14)
and the expansion about x0 + 2Δx
f(x0 + 2Δx) = f(x0) + 2f ′(x0)Δx + 2f ′′(x0)Δx2 +
4
3
f ′′′(x0)Δx3 + . . . (15)
3Let us subtract these last two equations to obtain
4f(x0 + Δx)− f(x0 + 2Δx) = 3f(x0) + 2f ′(x0)Δx− 23f
′′′(x0)Δx3 + . . . (16)
and solve with respect to f ′(x0)
f ′(x0) ≈ −3f(x0) + 4f(x0 + Δx)− f(x0 + 2Δx)2Δx + O(Δx
2) (17)
Thus, we arrived at the second-order accurate forward difference approximation. Notice, however, that to achieve this
we need to use the values of f(x) at 3 points, i.e., f(x0), f(x0 + Δx), and f(x0 + 2Δx).
Similarly, we can derive the second-order accurate backward difference, which is given by
f ′(x0) ≈ 3f(x0)− 4f(x0 −Δx) + f(x0 − 2Δx)2Δx + O(Δx
2) (18)
Exercise: Derive the last expression.
B. Finite differences: second order derivatives
In addition to first order derivatives, second order derivatives often appear in the physical sciences. We can use
Taylor expansions to also derive numerical approximations to second order derivatives. Lets see how we can achieve
this using centered differences.
Consider the Taylor expansions at x0 + Δx and x0 −Δx, i.e.,
f(x0 + Δx) = f(x0) + f ′(x0)Δx +
1
2
f ′′(x0)Δx2 +
1
3!
f ′′′(x0)Δx3 +
1
4!
f (4)(x0)Δx4 + . . . (19)
f(x0 −Δx) = f(x0)− f ′(x0)Δx + 12f
′′(x0)Δx2 − 13!f
′′′(x0)Δx3 +
1
4!
f (4)(x0)Δx4 . . . , (20)
where we included the term proportional to Δx4 as well. If instead of subtracting these expressions, we add them up
we obtain
f(x0 + Δx) + f(x0 −Δx) = 2f(x0) + f ′′(x0)Δx2 + 13!f
(4)(x0)Δx4 + . . . (21)
Solving the last expression wrt f ′′(x0) we find
f ′′(x0) =
f(x0 + Δx) + f(x0 −Δx)− 2f(x0)
Δx2
− 1
3!
f (4)(x0)Δx2 + . . . (22)
In other words the approximation to the second order derivative
f ′′(x0) ≈ f(x0 + Δx)− 2f(x0) + f(x0 −Δx)Δx2 + O(Δx
2) (23)
is second-order accurate, i.e., the leading order truncation error decays as Δx2. Using Taylor expansions one can
derive second-order accurate forward and backward difference approximations for the second derivative.
C. Convenient notation for computations
Let us introduce the notation that
fi = f(xi), (24)
then for uniformly spaced grids
fi+1 = f(xi+1) = f(xi + Δx), fi+2 = f(xi+2) = f(xi + 2Δx), etc. (25)
Thus, we can write the expressions for the derivative at each point xi of the grid as follows:
4• Second-order accurate centered difference for first order derivative:
f ′i = f
′(xi) =
fi+1 − fi−1
2Δx
(26)
• Second-order accurate forward difference for first order derivative:
f ′i =
−3fi + 4fi+1 − fi+2
2Δx
(27)
• Second-order accurate backward difference for first order derivative:
f ′i =
fi−2 − 4fi−1 + 3fi
2Δx
(28)
• Second-order accurate centered difference for second order derivative:
f ′′i =
fi−1 − 2fi + fi+1
Δx2
(29)
Exercise: α) Consider f(x) = ln(x) given in [1, 2]. Compute the derivative of the function at x = 1.5: i) using the
exact expression, and ii) assuming Δx = 0.1 using
• the first-order accurate forward difference and its relative error with respect to the exact value
• the second-order accurate forward difference and its relative error with respect to the exact value
• the second-order accurate centered difference and its relative error with respect to the exact value
Which numerical approximation has the smallest relative error?
β) Use your code from the Lagrange interpolation as template to write a code that splits the interval [a, b] = [1, 2]
in n equidistant intervals, i.e, n + 1 points, where you will define f(xi). This can be achieved if you define the grid
points xi = a + i ∗ (b− a)/n, i = 0, 1, 2, . . . n. Thus, the grid consists of n + 1 grid points. The particular form of the
grid points implies that the uniform grid spacing is Δx = xi+1 − xi = (b− a)/n. Follow these steps:
• Declare an array (xi) of size n + 1, construct a loop and save the coordinate points xi into the array xi.
• Declare an array (yi) of size n + 1, construct a loop and save the value f(xi) at the coordinate points xi into
the array yi. For convenience introduce a function f(x) = ln(x) to do these computations. This way your code
will be modular as you can change f(x) if you wish to.
• Compute f ′(x) ∀x ∈ (a, b) using i) second-order accurate centered differences, ii) forward differences and iii)
backward differences. It may be convenient to introduce 3 functions that take the arrays xi, yi, and n as
inputs, and you pass to them an array (pointer) dfdxi which will be assigned the values of the approximate
derivative. You can introduce 3 different dfdxi arrays of size n-1, e.g., dfdxi1, dfdxi2, dfdxi3, which will
store the approximate derivatives computed with each method separately.
• Compute the L2 norm of the errors for each of the 3 approximations. For a quantity Fi approximated by F˜i,
the L2 of the errors is given by
L2(δF ) =
√∑
i=1
(
Fi − F˜i
)2
, (30)
where here the sum is over the some grid points (x ∈ (a, b)), and obviously in our problem Fi = f ′i . It may also
be convenient to introduce a function for computing the analytic derivative of the function so that your code is
modular if it is provided f(x) and df/dx.
5• Run your code for n = 10, 20, 40, 80, 160, 320, and plot the L2 norm of the errors keeping the same points only
(independently of n) vs Δx. Since we are using fwd and bwd difference the calculation computing the derivative
at points that are the same for all approximations requires that the first two grid points (x[0], x[1]), and the
last two grid points (x[n-2], x[n-1]) be excluded from the computation. The L2 must be computed at the
same grid points (i.e. the same coordinates) for all values of n. Given that n = 10 has the fewest grid points,
only the 6 grid points at n = 10 should be used in the computation of L2 norms. This can be achieved if you
have a loop over the grid that looks like this
for(int i=2;iif (i % (n/10)==0){ // the "%" symbol is the modulo function
"here you will do the L2 norm computation
}
}
Does the error decay as Δx2 for each of the 3 methods. After running your code create a file with 4 columns:
“dx”, “L2 norm centered”, “L2 norm forward”, “L2 norm backward”. Then make a plot the 3 different L2
norms vs dx. What kind of plot do you need to make to show that the errors decay as Δx2.
D. General formula
Consider the set of n+1 points {x0, x1, . . . , xn} in some interval I, where f ∈ C∞, then from Lagrange interpolation
we know
f(x) =
n∑
k=0
f(xk)Lk(x) +
(x− x0) . . . (x− xn)
(n + 1)!
f (n+1)(ξ(x)), (31)
where
Lk =
(x− x0)(x− x1) . . . (x− xk−1)(x− xk+1) . . . (x− xn)
(xk − x0)(xk − x1) . . . (xk − xk−1)(xk − xk+1) . . . (x + k − xn) (32)
Using the Lagrange approximant we can compute the derivative, i.e.,
f ′(x) =
n∑
k=0
f(xk)L′k(x) + Dx
[
(x− x0) . . . (x− xn)
(n + 1)!
]
f (n+1)(ξ(x)) +
(x− x0) . . . (x− xn)
(n + 1)!
Dx
(
f (n+1)(ξ(x))
)
, (33)
If we evaluate the last expression at any x = xj the last term vanishes and we are left with
f ′(xj) =
n∑
k=0
f(xk)L′k(xj) +
f (n+1)(ξ(xj))
(n + 1)!
n∏
k=0,k 6=j
(xj − xk) (34)
Equation (34) is called the (n + 1)-point formula approximating f ′(xj). In practice using large n is very expensive.
The power of approximations to derivatives obtained with this method is that the approximants can be applied to
non-uniformly spaced grids.
Let us now obtain derivative expressions for 3-point formulas, for which we have
L0(x) =
(x− x1)(x− x2)
(x0 − x1)(x0 − x2) , L1(x) =
(x− x0)(x− x2)
(x1 − x0)(x1 − x2) , L2(x) =
(x− x0)(x− x1)
(x2 − x0)(x2 − x1) ,
L′0(x) =
2x− x1 − x2
(x0 − x1)(x0 − x2) , L

1(x) =
2x− x0 − x2
(x1 − x0)(x1 − x2) , L

2(x) =
2x− x0 − x1
(x2 − x0)(x2 − x1)
(35)
Thus, applying Eq. (34) to obtain the 3-point formulae for the first order derivative we have
f ′(xj) =
2∑
k=0
f(xk)L′k(xj) +
f (3)(ξ(xj))
3!
2∏
k=0,k 6=j
(xj − xk) (36)
6which if we expand becomes
f ′(xj) = f(x0)L′0(xj) + f(x1)L

1(xj) + f(x2)L

2(xj) +
f (3)(ξ(xj))
3!
2∏
k=0,k 6=j
(xj − xk) (37)
Notice that this formula applies even if the points are not equidistant, i.e., x2 − x1 6= x1 − x0. For uniformly spaced
grids we have x2 = x1 + Δx = x0 + 2Δx, and x1 = x0 + Δx. Then if we set xj = x0, x1 = x0 + Δx, x2 = x0 + 2Δx
the expressions (37) simplify to
f ′(x0) =
1
Δx
[
−3
2
f(x0) + 2f(x1)− 12f(x2)
]
+
Δx2
3
f ′′′(ξ0)
f ′(x1) =
1
Δx
[
−1
2
f(x0) +
1
2
f(x2)
]
+
Δx2
6
f ′′′(ξ1)
f ′(x2) =
1
Δx
[
1
2
f(x0)− 2f(x1) + 32f(x2)
]
+
Δx2
3
f ′′′(ξ2)
(38)
Since x1 = x0 + Δx, x2 = x0 + 2Δx the last can be re-written as
f ′(x0) =
1
2Δx
[−3f(x0) + 4f(x0 + Δx)− f(x0 + 2Δx)] + O(Δx2)
f ′(x0 + Δx) =
1
Δx
[
−1
2
f(x0) +
1
2
f(x0 + 2Δx)
]
+ O(Δx2)
f ′(x0 + 2Δx) =
1
Δx
[
1
2
f(x0)− 2f(x0 + Δx) + 32f(x0 + 2Δx)
]
+ O(Δx2)
(39)
The first equation in Eq. (39) is the familiar second-order accurate forward difference we derived earlier. The other
two equations can be recast in the centered and backward differences we derived as follows. For the second equation
we introduce a new variable x0 + Δx = xˉ0, and then the equation can be written as
f ′(xˉ0) =
1
2Δx
[−f(xˉ0 −Δx) + f(xˉ0 + Δx)] + O(Δx2) (40)
which is the second-order accurate centered finite difference formula. For the third equation similarly we introduce a
new variable x0 + 2Δx = x˜0, and then we obtain
f ′(x˜0) =
1
2Δx
[f(x˜0 − 2Δx)− 4f(x˜0 −Δx) + 3f(x˜0)] + O(Δx2), (41)
which is the second-order accurate backward finite difference formula. Obviously the 3-point formulas are 2nd-order
accurate.
E. Higher-order approximations
Similarly to the 3-point formulae, one can derive the 5-point formulae, which will be 4th-order accurate. For
example, the centered difference is given by
f ′(x0) =
1
12Δx
[f(x0 − 2Δx)− 8f(x0 −Δx) + 8(x0 + Δx)− f(x0 + 2Δx)] + O(Δx4), (42)
and the forward difference five-point formula is
f ′(x0) =
1
12Δx
[−25f(x0) + 48f(x0 + Δx)− 36(x0 + 2Δx) + 16f(x0 + 3Δx)− 3f(x0 + 4Δx)] + O(Δx4). (43)
7II. ROUND-OFF ERROR INSTABILITY
Consider the 2nd-order accurate centered difference approximation for the first order derivative
f ′(x0) =
f(x0 + Δx)− f(x0 −Δx)
2Δx
− Δx
2
6
f ′′′(ξ) (44)
In a computer the expressions f(x0 ±Δx) have a round-off error e(x0 ±Δx), i.e., what we input is f˜(x0 ±Δx) =
f(x0 ±Δx) + e(x0 ±Δx). This implies that what we compute is
f ′(x0) =
f˜(x0 + Δx)− f˜(x0 −Δx)
2Δx
− Δx
2
6
f ′′′(ξ) (45)
or
f ′(x0)− f(x0 + Δx)− f(x0 −Δx)2Δx =
e(x0 + Δx)− e(x0 −Δx)
2Δx
− Δx
2
6
f ′′′(ξ) (46)
If |e(x0 ±Δx)| ≤ ², and |f ′′′(ξ)| ≤ M , then∣∣∣∣f ′(x0)− f(x0 + Δx)− f(x0 −Δx)2Δx
∣∣∣∣ ≤ ²Δx + Δx26 M (47)
This last expression implies that if Δx ↓, Δx2 ↓ so the truncation error ↓. But, ²Δx ↑. Hence, we cannot expect
the error to always go down as Δx ↓. Thus, when decreasing Δx from a large value the total error goes down and
at some point it will begin to increase. The turnover point occurs when ²Δx ' Δx
2
6 M . Thus, if we take the function
ln x in the interval [1, 2], f ′′′(x) = 2/x3, which is f ′′′(x) ≤ 2 for x ∈ [1, 2]. Assuming 64-bit floating point arithmetic
² ' 10−16. Thus, solving ²Δx ' Δx
2
6 M for Δx, yields Δx = (6²/M)
1/3, i.e., Δx = (3× 10−16)1/3 ≈ ×10−6. Thus, for
Δx . 10−6, we would expect the error in evaluating the derivative to begin to increase. You will test this expectation
in the 3rd homework set.
III. RICHARDSON EXTRAPOLATION
The Richardson extrapolation can be applied when an approximation method has a predictable truncation error
term, that depends on a single parameter (usually the grid spacing or step size δx). When we are able to apply it,
Richardson extrapolation provides a way to obtain more accurate expressions by using less accurate results. As a
result, Richardson extrapolation (RE) can be used to also obtain error estimates.
Let N1(Δx) approximate a constant M , i.e.,
N −N1(Δx) = k1Δx + k2Δx2 + k3Δx3, (48)
for unknown k1, k2, k3. The leading order error in the last expression is O(Δx), i.e.,
M −N1(Δx) ≈ k1Δx. (49)
RE combines the “inaccurate” O(Δx) approximation N1(Δx) to produce more accurate approximations. Let us
consider
M = N1(Δx) + k1Δx + k2Δx2 + k3Δx3 + . . .
M = N1(
Δx
2
) + k1
Δx
2
+ k2
Δx2
4
+ k3
Δx3
8
+ . . .
(50)
If we subtract the former equation from twice the latter equation we obtain
M = N1(
Δx
2
) + [N1(
Δx
2
)−N1(Δx)] + k2[Δx
2
2
−Δx2] + k3[Δx
3
4
−Δx3] . . . (51)
Now, let
8N2(Δx) = N1(
Δx
2
) + [N1(
Δx
2
)−N1(Δx)]. (52)
Then
M = N2(Δx)− k2Δx2 − 3k34 Δx
3 . . . (53)
is O(Δx2)!
RE can be applied whenever the truncation error has the form
∑m−1
j=1 kjΔx
aj + O(Δxam), for some kj , with
a1 < a2 < . . . < am.
Many formulae will have only even powers of Δx in the truncation error, e.g., centered finite difference formula for
f ′(x),
M = N1(Δx) + k1Δx2 + k2Δx4 + k3Δx6 + . . .
M = N1(
Δx
2
) + k1
Δx2
4
+ k2
Δx4
16
+ k3
Δx6
64
+ . . .
(54)
Multiply the last equation by 4, and subtract the two equations to obtain
M =
1
3
[
4N1(
Δx
2
)−N1(Δx)
]
− k2
4
Δx4 − 5k3
16
Δx6 + . . . (55)
Now let
N2(Δx) =
1
3
[
4N1(
Δx
2
)−N1(Δx)
]
(56)
Then the approximation
M = N2(Δx)− k24 Δx
4 + . . . . (57)
In other words, having 2 different resolutions at O(Δx2) accuracy with the N1 operator we can combine them to
obtain an O(Δx4) approximation!
Now, consider the N2 approximant and take h → h/2. We have
M = N2(
h
2
)− k2
64
Δx4 − k′3Δx6 . . . (58)
It is straightfoward to show that the approximant
N3(Δx) =
1
15
[
16N2
(
Δx
2
)
−N2 (Δx)
]
(59)
is
M = N3(Δx) + O(Δx6) . . . (60)
In other words, if we have 3 resolutions using the N1 approximant at 2nd-order accuracy, we can combine them
to achieve an approximation that is 6-th order accurate!!!!!!!!!!!!!! This procedure can be done iteratively to obtain
higher-accuracy representations, but in practice we rarely have more than 3 different resolutions. Thus, going up to
N3 is most common.
MTH6150: Integration
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
In this note we discuss methods for computing numerical integrals.
I. NUMERICAL INTEGRATION
Just as in the case of differentiation, we often need to integrate data without having analytic functions to work
with. Computing integrals of functions/data known at a finite number of grid points is called numerical integration.
There are multiple ways one can approximate an integral numerically, and we will review here some of these methods
including the trapezoidal, Simpson’s 3-point, Gaussian quadrature, and Monte-Carlo techniques. We will also consider
mutliple integrals.
II. NUMERICAL INTEGRATION IN ONE DIMENSION
An approximation to the integral ∫ b
a
f(x)dx (1)
is called a numerical quadrature, and uses a weighted sum of f at a finite number of points in [a, b], i.e.,∫ b
a
f(x)dx =

i
aif(xi). (2)
So, we select a set of distinct nodes (points) {x0, x1, . . . xn} ∈ [a, b], and then base our methods on the Lagrange
interpolation polynomial
f(x) ≈ Pn(x) =
n∑
i=0
f(xi)Li(x) +
n∏
i=0
(x− xi)f
(n+1)(ξ(x))
(n + 1)!
(3)
Substituting Eq. (3) in Eq. (1) we obtain∫ b
a
f(x)dx =
∫ b
a
n∑
i=0
f(xi)Li(x)dx +
1
(n + 1)!
∫ b
a
n∏
i=0
(x− xi)f (n+1)(ξ(x))dx (4)
or ∫ b
a
f(x)dx =
n∑
i=0
aif(xi) +
1
(n + 1)!
∫ b
a
n∏
i=0
(x− xi)f (n+1)(ξ)dx, (5)
where
ai =
∫ b
a
Li(x)dx, i = 0, . . . n (6)
A. Basic rules for uniformly spaced grids
1. Trapezoidal method
We will assume that x0 = a, x1 = b and define h = b − a. Through 2 points we can fit a first-order Lagrange
polynomial, i.e., a straight line
P1(x) =
x− x1
x0 − x1 f(x0) +
x− x0
x1 − x0 f(x1) +
1
2
(x− x0)(x− x1)f ′′(ξ(x)) (7)
2Then the quadrature of f is∫ b
a
f(x)dx =
∫ x1
x0
[
x− x1
x0 − x1 f(x0) +
x− x0
x1 − x0 f(x1)
]
dx +
1
2
∫ x1
x0
(x− x0)(x− x1)f ′′(ξ(x))dx (8)
At this point it is instructive to review the Weighted Mean Value theorem for integrals: Let f be continuous on [a, b]
and g an integrable function on [a, b] that does not change sign on [a, b], then∫ b
a
f(x)g(x)dx = f(c)
∫ b
a
g(x)dx, (9)
for some c in (a, b).
In the second term on the right-hand-side of Eq. (8) the function g(x) = (x− x0)(x− x1) does not change sign on
[a, b]. Thus, based on the Weighted Mean Value theorem for integrals
1
2
∫ x1
x0
(x− x0)(x− x1)f ′′(ξ(x))dx = f ′′(ξ)12
∫ x1
x0
(x− x0)(x− x1)dx = f ′′(ξ) (x0 − x1)
3
12
= −f ′′(ξ)h
3
12
, (10)
for some ξ ∈ (a, b). By use of Eq. (10), Eq. (8) after integrating the first term yields∫ b
a
f(x)dx =
x1 − x0
2
[f(x0) + f(x1)]− f ′′(ξ)h
3
12
, (11)
So, we arrive at the trapezoidal rule for uniformly spaced grids∫ b
a
f(x)dx = h
(
f(x0) + f(x1)
2
)
− f ′′(ξ)h
3
12
, (12)
Notice that the leading order truncation error term is O(h3).
The geometric interpretation of the trapezoidal rule is shown in Fig. 1
f(a)
f(b)F(x)
P1(x)
0.5 1.0 1.5
x
0.5
1.0
1.5
2.0
f(x)
FIG. 1. Trapezoidal approximation to a numerical quadrature. We fit a first-order Lagrange polynomial, i.e, a line, and
approximate the integral (area under f(x)) from x = a to x = b by the trapezoidal shaded area, whose area simply is
[f(a) + f(b)](b− a)/2 in agreement with the expression (12).
32. Simpson’s 3-point formula
We will now consider three points. Through 3 points we can fit a second-order Lagrange polynomial, i.e.,
P2(x) =
(x− x1)(x− x2)
(x0 − x1)(x0 − x2)f(x0) +
(x− x0)(x− x2)
(x1 − x0)(x1 − x2)f(x1) +
(x− x0)(x− x1)
(x2 − x0)(x2 − x1)f(x2) +
1
6
(x− x0)(x− x1)(x− x2)f ′′′(ξ(x))
(13)
The quadrature of f then becomes∫ b
a
f(x)dx =
∫ x2
x0
[
(x− x1)(x− x2)
(x0 − x1)(x0 − x2)f(x0) +
(x− x0)(x− x2)
(x1 − x0)(x1 − x2)f(x1) +
(x− x0)(x− x1)
(x2 − x0)(x2 − x1)f(x2)
]
dx
+
1
6
∫ x2
x0
(x− x0)(x− x1)(x− x2)f ′′′(ξ(x))dx
(14)
The first integral in Eq. (14) yields∫ x2
x0
[
(x− x1)(x− x2)
(x0 − x1)(x0 − x2)f(x0) +
(x− x0)(x− x2)
(x1 − x0)(x1 − x2)f(x1) +
(x− x0)(x− x1)
(x2 − x0)(x2 − x1)f(x2)
]
dx =
(x2 − x0)(2x0 − 3x1 + x2)
6(x0 − x1) f(x0) +
(x2 − x0)3
6(x1 − x0)(x1 − x2)f(x1) +
(x2 − x0)(x0 − 3x1 + 2x2)
6(x2 − x1) f(x2)
(15)
Equation (15) is Simpson’s general 3-point formula that applies to both uniformly spaced and non-uniformly-spaced
grids. If we assume that x0 = a, x1 = a + h, x2 = x1 + h with h = (b− a)/2, the formula becomes∫ b
a
f(x)dx ≈ h
3
[f(a) + 4f(
a + b
2
) + f(b)] (16)
Exercize 1: Consider the fact that f(a), f(b), f((b + c)/2) all have a round-off error that is bounded,
and assume the highest order derivative in the leading truncation error is also bounded. Is there a
round-off error instability in numerical quadratures computed with the trapezoidal or Simpson’s rules?
Explain.
Now, obtaining the error with this approach will yield an O(h4) leading truncation error, but the method is actually
O(h5). To demonstrate this we will provide an alternative derivation of Simpson’s 3-point formula which reveals the
true leading order truncation error.
Let’s consider the Taylor expansion up to 4th-order of f at x = x1
f(x) = f(x1) + f ′(x1)(x− x1) + 12f
′′(x1)(x− x1)2 + 16f
′′′(x1)(x− x1)3 + 124f
(4)(ξ(x))(x− x1)4 (17)
We can then substitute Eq. (17) in
∫ x2
x0
f(x)dx and perform the integration to obtain∫ x2
x0
f(x)dx =
[
f(x1)(x− x1) + 12f
′(x1)(x− x1)2 + 16f
′′(x1)(x− x1)3 + 124f
′′′(ξ(x1))(x− x1)4
]x2
x0
+
1
24
∫ x2
x0
f (4)(ξ(x))(x− x1)4dx
(18)
Note that the integral of f(x1) is just f(x1)x and not f(x1)(x− x1). However, since this is a definite integral we can
add a constant c = −f(x1)x1 and the result of the integration remains unaffected. The integral on the right-hand-side
of Eq. (18) is already in a form that allows us to use the Weighted Mean Value Theorem for integrals, because the
function g(x) = (x− x1)4 does not change sign in [x0, x2]. Thus,
1
24
∫ x2
x0
f (4)(ξ(x))(x− x1)4dx = 124f
(4)(ξ1)
∫ x2
x0
(x− x1)4dx = 1120f
(4)(ξ1)(x− x1)5
∣∣∣∣x2
x0
=
4
15
f (4)(ξ1)h5. (19)
The first term on the right-hand-side of Eq. (18) yields∫ x2
x0
f(x)dx = 2hf(x1) +
h3
3
f ′′(x1), (20)
4but the 2nd-order accurate centered difference for the second derivative is
f ′′(x1) =
1
h2
[f(x1 − h)− 2f(x1) + f(x1 + h)] + O(h2) = 1
h2
[f(x0)− 2f(x1) + f(x2)] + O(h2) (21)
Hence the last two equations and Eq. (19) imply that the Simpson 3-point rule is∫ x2
x0
f(x)dx =
h
3
[f(x0) + 4f(x1) + f(x2)] + O(h5), (22)
where the O(h5) leading order truncation error is now evident, and again this applies to uniformly spaced grids:
x1 = x0 + h, x2 = x1 + h = x0 + 2h. Notice that Eq. (22) is the same as Eq. (16) because x1 = (x0 + x2)/2.
The geometric interpretation of Simpson’s 3-point rule is shown in Fig. 1
f(a)
f(
a + b
2
)
f(b)f(x)
P2(x)
0.5 1.0 1.5
x
0.5
1.0
1.5
2.0
f(x)
FIG. 2. Simpson’s 3-point rule approximation to a numerical quadrature. We approximate the integral (area under f(x)) from
x = a to x = b by the area under the unique quadratic function that passes through points (a, f(a)), ((a + b)/2, f((a + b)/2)),
(b, f(b)).
For all practical purposes Simpson’s 3-point rule suffices to achieve a very accurate result, because it is 5th-order
convergent.
FIG. 3. Simpson’s 3-point rule in the case of a large integration domain. Notice how the area of the shaded region does not
match the area under the function f(x) because the spacing between the 3 points is large.
Exercise 2: Compute
∫ h
0
exp(x)dx: a) exactly, b) using the trapezoidal rule, and c) using Simpson’s
3-point rule. Use your results to calculate the absolute error (difference) of the Simpson result with
respect to the exact result. Make a plot of the relative error vs h. How fast does the absolute error
decay with h? Note that in gnuplot you can define functions by simply typing f(x)=exp(x).
B. Composite Integration
When using either the trapezoidal or Simpson’s 3-point rule, achieving an accurate result requires a sufficiently
small h. But, the domain of integration is typically going to be too large for, e.g., 3 points to yield an accurate result
as shown in Fig. 3. In this case we will break the interval into multiple small intervals and use either the trapezoidal
rule or Simpson’s 3-point rule. Geometrically this is shown in Fig. 4
50.5 1.0 1.5 2.0 2.5 3.0
x
2
4
6
8
f(x)
0.5 1.0 1.5 2.0 2.5 3.0
x
2
4
6
8
f(x)
FIG. 4. Geometric representation of composite integration. On the left is the composite trapezoidal rule. The alternating
colored shaded regions each of size h show the intervals that each separate trapezoidal rule is applied. Same as on the left but
for the composite Simpson’s 3-point rule. Notice that the shaded regions now have a size of 2h because we need 3-points to
apply Simpson’s 3-point rule.
1. Composite Simpson’s 3-point rule
For convenience we re-write Simpson’s 3-point rule∫ x2
x0
f(x)dx =
h
3
[f(x0) + 4f(x0 + h) + f(x0 + 2h)] . (23)
Our goal is to have an accurate representation of the integral
∫ b
a
f(x)dx. First we choose an even number n that
will divide the interval [a, b] into n subintervals. Then we will apply Simpson’s rule on each pair of subintervals. We
will define the grid spacing
h =
b− a
n
(24)
and the coordinate points of the subintervals
xi = a + j ∙ h, j = 0, 1, . . . n (25)
The integral can be analytically rewritten as∫ b
a
f(x)dx =
∫ x2
x0
f(x)dx +
∫ x4
x2
f(x)dx +
∫ x6
x4
f(x)dx + . . .
∫ xn
xn−2
f(x)dx =
n/2∑
i=1
∫ x2i
x2i−2
f(x)dx (26)
By use of Eq. (23) the last equation becomes∫ b
a
f(x)dx =
n/2∑
i=1
∫ x2i
x2i−2
f(x)dx =
h
3
n/2∑
i=1
[f(x2i−2) + 4f(x2i−1) + f(x2i)] =
h
3
n/2∑
i=1
f(x2i−2) + 4
n/2∑
i=1
f(x2i−1) +
n/2∑
i=1
f(x2i)]

(27)
The sum
∑n/2
i=1 f(x2i−2) can be recast in a more convenient form by introducing a new counter k = i − 1. For i = 1
then k = 0 and for i = n/2, k = n/2 − 1. Thus, the sum can be transformed to ∑n/2−1k=0 f(x2k). Given that k is a
dummy variable we can rename it back to i, hence
n/2∑
i=1
f(x2i−2) =
n/2−1∑
i=0
f(x2i) = f(x0) +
n/2−1∑
i=1
f(x2i) (28)
The last sum in Eq. (27) can also be rewritten as follows
n/2∑
i=1
f(x2i) = f(xn) +
n/2−1∑
i=1
f(x2i) (29)
6By substituting Eqs. (28) and (29) in (27) yields the final form of the composite Simpson’s 3-point rule
∫ b
a
f(x)dx =
h
3
f(x0) + f(xn) + 4 n/2∑
i=1
f(x2i−1) + 2
n/2−1∑
i=1
f(x2i)]
+ O(h4) (30)
Notice that the leading truncation error is O(h4) and not O(h5). This can be shown, but we will not show it here.
Despite the fact that the composite method is O(h4) it is still more accurate than the standard 3-point method
because h is much smaller in the composite method.
Algorithm for Simpson’s composite rule: Goal to approximate I =
∫ b
a
f(x)dx
1. INPUT: f(x), end points a, b and an even positive integer n.
OUTPUT: approximation to I.
2. Set h = (b− a)/n.
3. Set xI = f(a) + f(b); xI1 = 0 (sum of f(x2i−1)), xI2 = 0 (sum of f(x2i))
4. For i = 1, . . . n − 1 do steps 5 and 6.
5. Set x = a + ih
6. If (i is even) set xI2 = xI2 + f(x)
else set xI1 = xI1 + f(x)
7. Set xI = h(xI + 2 ∙ xI2 + 4 ∙ xI1)/3.
8. Return (xI)
2. Composite trapezoidal rule
Following a procedure similar to the one we followed for the composite Simpson’s rule, we can derive the composite
trapezoidal rule which is ∫ b
a
f(x)dx =
h
2
[
f(a) + 2
n−1∑
i=1
f(xi) + f(b)
]
+ O(h2), (31)
where again xi = a + jh, j = 0, 1, . . . n. Note that for the trapezoidal rule n can be either even or odd.
Algorithm for composite trapezoidal rule: Goal to approximate I =
∫ b
a
f(x)dx
1. INPUT: f(x), end points a, b and a positive integer n.
OUTPUT: approximation to I.
2. Set h = (b− a)/n.
3. Set xI = f(a) + f(b); xI1 = 0 (sum of f(xi))
4. For i = 1, . . . n − 1 do steps 5 and 6.
5. Set x = a + i ∙ h
6. Set xI1 = xI1 + f(x)
7. Set xI = h(xI + 2 ∙ xI1)/2.
8. Return (xI)
Exercize 3: Use the trapezoidal rule to compute the integral I =
∫ 1
−1 cos(x)dx. The exact value of the
integral is I = 2 sin(1) = 1.682941969615793. Run your code for n = 50,100,200,400. Plot the relative
error as a function of n. What value of n does your plot predict that you need to compute I to within
an accuracy of 1 part in 107? Run your code with your predicted value for n and measure the relative
error. Was your prediction correct?
7C. Gaussian Quadrature
So far we focused on uniformly spaced grid points (nodes). Gaussian Quadrature is an algorithm that chooses the
nodes such that in the approximation ∫ b
a
f(x)dx =
n∑
i=1
cif(xi) (32)
the error is minimized in the sense that the result is exact for the class of polynomials of degree 2n − 1. This is
because Gaussian Quadrature treats both ci and xi as parameters, i.e., there are 2n parameters which is the number
of parameters in a polynomial of degree 2n− 1.
For example, let’s consider the case n = 2 for x ∈ [−1, 1]. We wish to determine c1, c2, x1, x2 such that∫ 1
−1
f(x)dx =
n∑
i=1
cif(xi) = c1f(x1) + c2f(x2) (33)
is exact whenever f(x) is a polynomial of degree 2n− 1 = 2 ∙ 2− 1 = 3 or less, i.e.,
f(x) = a0 + a1x + a2x2 + a3x3. (34)
To achieve this we will first integrate this polynomial∫ 1
−1
f(x)dx = a0
∫ 1
−1
dx + a1
∫ 1
−1
xdx + a2
∫ 1
−1
x2dx + a3
∫ 1
−1
x3dx. (35)
We can now consider the function f(x) as a linear combination of the 4 functions f0(x) = 1, f1(x) = x, f2(x) = x2,
f3(x) = x3, i.e., f(x) = a0f0(x) + a1f1(x) + a2f2(x) + a3f3(x) =
∑3
i=0 aif(xi). Thus, if for each fi(x), i = 0, 1, 2, 3
we satisfy
∫ 1
−1 fi(x)dx = c1fi(x1) + c2fi(x2), then we will achieve Eq. (33) because∫ 1
−1
f(x)dx =
∫ 1
−1

i
aifi(x)dx =

i
ai
∫ 1
−1
fi(x)dx =

i
ai(c1fi(x1) + c2fi(x2)) =

i
aic1fi(x1) +

i
aic2fi(x2)
= c1

i
aifi(x1) + c2

i
aifi(x2) = c1f(x1) + c2f(x2).
(36)
Thus, for the 4 fi(x) polynomials we need to satisfy
∫ 1
−1 fi(x)dx = c1fi(x1)+ c2fi(x2), which will provide 4 equations
for the 4 parameters c1, c2, x1, x2:
c1f0(x1) + c2f0(x2) =c1 ∙ 1 + c2 ∙ 1 =
∫ 1
−1
1dx = 2
c1f1(x1) + c2f1(x2) =c1 ∙ x1 + c2 ∙ x2 =
∫ 1
−1
xdx = 0
c1f2(x1) + c2f2(x2) =c1 ∙ x21 + c2 ∙ x22 =
∫ 1
−1
x2dx =
2
3
c1f3(x1) + c2f3(x2) =c1 ∙ x31 + c2 ∙ x32 =
∫ 1
−1
x3dx = 0.
(37)
This system can be solved easily by eliminating c1 and c2 from the first two equations, e.g., solve the first w.r.t c1
and then replace the result in the second equation to express c2 in terms of x1 and x2. Then substitute c1 and c2 in
terms of x1 and x2 in the last two equations that will provide a system of 2 equations for the 2 unknowns x1 and x2.
The solution of the system is c1 = 1, c2 = 1, x1 = −

3/3 = −x2. Therefore, the n = 2 Gaussian quadrature is∫ 1
−1
f(x)dx = f(−

3
3
) + f(

3
3
), (38)
which yields an exact result for any f(x) that is a polynomial up to degree n = 3!
The same approach can yield results for degree 2n − 1 polynomials, but this turns out to be more involved. By
using Legendre polynomials, the process can be expedited. So, let’s introduce the Legendre polynomials first.
Legendre polynomials (Pn(x)) are orthogonal and form a complete basis. Some of their properties are:
81. For each n, Pn(x) is a monic polynomial of degree n, i.e., the coeficient of the smallest power of x that appears
in the polynomial is 1.
2.
∫ 1
−1 P (x)Pn(x) = 0, when P (x) is a polynomial of degree < n.
3. The first 5 Legenre polynomials are: P0(x)=1, P1(x)=x, P2(x) = x2 − 1/3, P3(x) = x3 − 3x/5,
P4(x) = x4 − 6x2/7 + 3/35.
4. Theorem
Assume x1, x2, . . . xn are the roots of the n-th Legendre polynomial Pn(x) and for each i = 1, 2, . . . n, consider
the numbers
ci =
∫ 1
−1
n∏
j=1,j 6=i
x− xj
xi − xj dx (39)
If P (x) is any polynomial of degree less than 2n− 1, then∫ 1
−1
P (x)dx =
n∑
i=1
ciP (xi). (40)
Let’s consider the integral
I =
∫ 1
−1
excos(x)dx = 1.933421496200713, (41)
and lets approximate it using Gaussian quadrature with n = 3, i.e.,
I˜ = c1f(x1) + c2f(x2) + c3f(x3). (42)
where x1, x2, x3 are the roots of the Legendre polynomial P3(x), i.e., x3 − 3x/5 = 0 ⇒ x = 0 or x2 − 3/5 = 0 ⇒
x = ±√3/5. Thus, x1 = −√3/5, x2 = 0, and x3 =√3/5. Then
c1 =
∫ 1
−1
n∏
j=1,j 6=1
x− xj
x1 − xj dx =
∫ 1
−1
x− x2
x1 − x2
x− x3
x1 − x3 dx =
5
9
c2 =
∫ 1
−1
n∏
j=1,j 6=2
x− xj
x2 − xj dx =
∫ 1
−1
x− x1
x2 − x1
x− x3
x2 − x3 dx =
8
9
c3 =
∫ 1
−1
n∏
j=1,j 6=3
x− xj
x3 − xj dx =
∫ 1
−1
x− x1
x3 − x1
x− x2
x3 − x2 dx =
5
9
(43)
Thus,
I˜ =
5
9
f
(


3
5
)
+
8
9
f(0) +
5
9
f
(√
3
5
)
= 1.933390469264298. (44)
Thus, the relative error is |(I˜ − I)/I| = 1.6 × 10−5!!! Notice that Eq. (44) applies to any function, and is exact for
any polynomial of defree 5 or less.
Arbitrary intervals: The general procedure for Gaussian quadrature outlined earlier applies to the interval [−1, 1].
However, we will often have general integration domains [a, b]. We can still apply Eq. (40) by a simple transformation
of the integration variable. An integral of the form ∫ b
a
f(x)dx (45)
can easily be transformed to the form ∫ 1
−1
f(t)dt (46)
9if we let
t =
2x− a− b
b− a , (47)
which implies
x =
1
2
[(b− a)t + a + b]. (48)
Thus, when x = a, t = −1, and when x = b, t = 1. We then have dx = b−a2 dt, and hence∫ b
a
f(x)dx =
b− a
2
∫ 1
−1
f
(
(b− a)t + (b + a)
2
)
dt. (49)
The last expression allows the application of Eq. (40) to any function in any integration interval.
Exercize 4: Use the n = 3 Gaussian quadrature method to compute the integral I =
∫ π
0
x cos(x)dx.
The exact value of the integral is I = −2. What is the relative error of the n = 3 Gaussian quadrature?
Answer: 0.00406
III. NUMERICAL INTEGRATION IN MULTIPLE DIMENSIONS: MULTIPLE INTEGRALS
We will consider double integrals because the extension to three dimensions is straightforward. We use to approxi-
mate the integral ∫ ∫
f(x, y)dxdy (50)
We will use properties of multiple integrals to write this as∫ ∫
f(x, y)dxdy =
∫ b
a
(∫ d
c
f(x, y)dy
)
dx (51)
In other words first we will approximate the integral in the parenthesis
∫ d
c
f(x, y)dy and then we will do the integral
over dx.
A. Trapezoidal rule
Using the trapezoidal rule the integral
∫ d
c
f(x, y)dy becomes∫ d
c
f(x, y)dy =
f(x, c) + f(x, d)
2
(d− c) + O([d− c]2) (52)
Then we have to integrate∫ ∫
f(x, y)dxdy =
∫ b
a
(∫ d
c
f(x, y)dy
)
dx =
∫ b
a
(
f(x, c) + f(x, d)
2
(d− c) + O([d− c]2)
)
dx
=
d− c
2
(∫ b
a
f(x, c)dx +
∫ b
a
f(x, d)dx
)
+
∫ b
a
O[(d− c)2]dx
(53)
For which we can use the trapezoidal formula again to obtain∫ ∫
f(x, y)dxdy =
d− c
2
(
f(a, c) + f(b, c)
2
(b− a) + f(a, d) + f(b, d)
2
(b− a) + O[(b− a)2]
)
+ O[(d− c)2(b− a)]
(54)
10
1/4
1/4
1/4
1/4
a b
c
d
FIG. 5. Stencil coefficients for 2D trapezoidal integration. Each vertex value is multiplied by the weight 1/4, then the values
summed over and multiplied by the area of the rectangle.
or ∫ ∫
f(x, y)dxdy =
f(a, c) + f(b, c) + f(a, d) + f(b, d)
4
(d− c)(b− a) + O[(b− a)2(d− c) + (d− c)2(b− a)] (55)
In other words, the trapezoidal rule for the integral is equal to the average value of f(x, y) within the rectangle
x ∈ [a, b], y ∈ [c, d] times the area of the rectangle (d− c)(b− a). This is depicted in Fig. 5
We can also apply the composite trapezoidal rule in the rectangle [a, b] [c, d], using as midpoints the coordinates
(a + b)/2 and (c + d)/2. Then it is straightforward to show
∫ ∫
f(x, y)dxdy =
(d− c)(b− a)
16
[f(a, c) + f(b, c) + f(a, d) + f(b, d)
+2
(
f
(
a + b
2
, c
)
+ f
(
a + b
2
, d
)
+ f
(
a,
c + d
2
)
+ f
(
b,
c + d
2
))
+ 4f
(
a + b
2
,
c + d
2
)]
+ O
(
(b− a)(d− c)[(d− c)2 + (b− a)2])
(56)
which is depicted in Fig. 6
B. Simpson’s rule
We can also perform the integration using Simpson’s rule∫ d
c
f(x, y)dy =
f(x, c) + f(x, d) + 4f(x, c+d2 )
2
(d− c) + O([d− c]5) (57)
and again we have∫ ∫
f(x, y)dxdy =
∫ b
a
(∫ d
c
f(x, y)dy
)
dx =
∫ b
a
(
f(x, c) + f(x, d) + 4f(x, c+d2 )
2
(d− c) + O[(d− c)5]
)
dx
=
d− c
2
(∫ b
a
f(x, c)dx +
∫ b
a
f(x, d)dx + 4
∫ b
a
f(x,
c + d
2
)dx
)
+
∫ b
a
O[(d− c)5]dx
(58)
11
1/16
1/8
1/16
1/8
1/4
1/8
1/16
1/8
1/16
a
a+b
2
b
c
c+d
2
d
FIG. 6. Stencil coefficients for 2D composite trapezoidal integration. Each vertex value is multiplied by the weight shown, then
the values summed over and multiplied by the area of the rectangle.
for which we can use Simpson’s 3-point rule again to obtain∫ ∫
f(x, y)dxdy =
d− c
2
[(
f(a, c) + f(b, c) + 4f
(
a + b
2
, c
))
b− a
2
+ O[(b− a)5]
+
(
f(a, d) + f(b, d) + 4f
(
a + b
2
, d
))
b− a
2
+ O[(b− a)5]
+ 4
(
f(a,
c + d
2
) + f(b,
c + d
2
) + 4f
(
a + b
2
,
c + d
2
))
b− a
2
+ O[(b− a)5]
]
+ O[(d− c)5(b− a)]
(59)
which simplifies to∫ ∫
f(x, y)dxdy =
(b− a)(d− c)
4
[(
f(a, c) + f(b, c) + f(a, d) + f(b, d)
)
+ 4
(
f
(
a + b
2
, c
)
+ f
(
a + b
2
, d
)
+ f(a,
c + d
2
) + f(b,
c + d
2
)
)
+ 16f
(
a + b
2
,
c + d
2
))]
+ O[(d− c)(b− a)5] + (d− c)5(b− a)]
(60)
This stencil is depicted in Fig. 7
C. Composite Simpson’s rule
Let ’s now apply the composite Simpson’s rule in double integrals. We will again use the property∫ ∫
f(x, y)dxdy =
∫ b
a
(∫ d
c
f(x, y)dy
)
dx (61)
First let yj = c + j ∙ hy, j = 0, 1, . . .m with hy = (d− c)/m and m is an even number. Then(∫ d
c
f(x, y)dy
)
=
hy
3
[
f(x, y0) + 2
(m/2)−1∑
j=1
f(x, y2j) + 4
m/2∑
j=1
f(x, y2j−1) + f(x, ym)
]
(62)
12
1/4
1
1/4
1
4
1
1/4
1
1/4
a
a+b
2
b
c
c+d
2
d
FIG. 7. Stencil coefficients for 2D Simpson’s integration. Each vertex value is multiplied by the weight shown, then the values
summed over and multiplied by the area of the rectangle.
Then Eq. (61) becomes
∫ ∫
f(x, y)dxdy =
∫ b
a
hy
3
[
f(x, y0) + 2
(m/2)−1∑
j=1
f(x, y2j) + 4
m/2∑
j=1
f(x, y2j−1) + f(x, ym)
] dx
=
hy
3
[ ∫ b
a
f(x, y0)dx + 2
(m/2)−1∑
j=1
∫ b
a
f(x, y2j)dx + 4
m/2∑
j=1
∫ b
a
f(x, y2j−1)dx +
∫ b
a
f(x, ym)dx
] (63)
Now, we need to define a discrete grid in the x direction, so we let xi = a + i ∙ hx, i = 0, 1, . . . n with hx = (b− a)/n,
where again n must be an even number. We will next use the composite Simpon’s rule to treat each of the 4 integrals
appearing in Eq. (63) separately:
∫ b
a
f(x, y0)dx =
hx
3
[
f(x0, y0) + 2
(n/2)−1∑
i=1
f(x2i, y0) + 4
n/2∑
i=1
f(x2i−1, y0) + f(xn, y0)
]
(64)
∫ b
a
f(x, ym)dx =
hx
3
[
f(x0, ym) + 2
(n/2)−1∑
i=1
f(x2i, ym) + 4
n/2∑
i=1
f(x2i−1, ym) + f(xn, ym)
]
(65)
∫ b
a
f(x, y2j)dx =
hx
3
[
f(x0, y2j) + 2
(n/2)−1∑
i=1
f(x2i, y2j) + 4
n/2∑
i=1
f(x2i−1, y2j) + f(xn, y2j)
]
(66)
∫ b
a
f(x, y2j−1)dx =
hx
3
[
f(x0, y2j−1) + 2
(n/2)−1∑
i=1
f(x2i, y2j−1) + 4
n/2∑
i=1
f(x2i−1, y2j−1) + f(xn, y2j−1)
]
(67)
Let’s now treat the last two integrals because these are further summed over j in (63). In particular, we have the
13
term
2
(m/2)−1∑
j=1
∫ b
a
f(x, y2j)dx =
hx
3
[
2
(m/2)−1∑
j=1
f(x0, y2j) + 4
(m/2)−1∑
j=1
(n/2)−1∑
i=1
f(x2i, y2j) + 8
(m/2)−1∑
j=1
n/2∑
i=1
f(x2i−1, y2j) + 2
(m/2)−1∑
j=1
f(xn, y2j)
] (68)
and the term
4
m/2∑
j=1
∫ b
a
f(x, y2j−1)dx =
hx
3
[
4
m/2∑
j=1
f(x0, y2j−1) + 8
m/2∑
j=1
(n/2)−1∑
i=1
f(x2i, y2j−1) + 16
m/2∑
j=1
n/2∑
i=1
f(x2i−1, y2j−1) + 4
m/2∑
j=1
f(xn, y2j−1)
] (69)
To sum up Eq. (63) becomes ∫ ∫
f(x, y)dxdy =
hxhy
9
(I1 + I2 + I3 + I4), (70)
where
I1 = f(x0, y0) + 2
(n/2)−1∑
i=1
f(x2i, y0) + 4
n/2∑
i=1
f(x2i−1, y0) + f(xn, y0), (71)
I2 = f(x0, ym) + 2
(n/2)−1∑
i=1
f(x2i, ym) + 4
n/2∑
i=1
f(x2i−1, ym) + f(xn, ym), (72)
I3 = 2
(m/2)−1∑
j=1
f(x0, y2j) + 4
(m/2)−1∑
j=1
(n/2)−1∑
i=1
f(x2i, y2j) + 8
(m/2)−1∑
j=1
n/2∑
i=1
f(x2i−1, y2j) + 2
(m/2)−1∑
j=1
f(xn, y2j), (73)
and
I4 = 4
m/2∑
j=1
f(x0, y2j−1) + 8
m/2∑
j=1
(n/2)−1∑
i=1
f(x2i, y2j−1) + 16
m/2∑
j=1
n/2∑
i=1
f(x2i−1, y2j−1) + 4
m/2∑
j=1
f(xn, y2j−1). (74)
The same principles can be applied to perform the double integral using the composite trapezoidal rule or Gaussian
integration, but we will not expand on the latter further. The former is an homework exercize.
D. Non-rectangular integration domains
We can also have integration in non-rectangular domains, e.g.,
∫ b
a
∫ d(x)
c(x)
f(x, y)dxdy or
∫ d
c
∫ b(y)
a(y)
f(x, y)dxdy (75)
Let ’s consider the left integral, as the right one can be treated in a similar way. Simpson’s 3-point rule for∫ b
a
∫ d(x)
c(x)
f(x, y)dxdy (76)
14
d(x)
c(x)
c (x) + d (x)
2
a
a+b
2
b
FIG. 8. Depiction of Simpson’s method for a double integral in a non-rectangular integration domain. In this case the step
size of the integration over y depends on x, i.e., it is variable.
will have a fixed step size for x, i.e., hx = (b− a)/2., but the step size for the integration over y, will have a variable
step size hy(x) = (d(x)− c(x))/2. As depicted in Fig. 8
We will again use the multiple integral property∫ b
a
∫ d(x)
c(x)
f(x, y)dxdy =
∫ b
a
(∫ d(x)
c(x)
f(x, y)dy
)
dx (77)
Simpson’s method for the interior integral gives∫ d(x)
c(x)
f(x, y)dy =
hy(x)
3
[f(x, c(x)) + 4f(x, c(x) + hy(x)) + f(x, d(x)] (78)
Substituting the last expression in Eq. (77) and applying Simpson’s rule yields∫ b
a
∫ d(x)
c(x)
f(x, y)dxdy =
∫ b
a
(
hy(x)
3
[f(x, c(x)) + 4f(x, c(x) + hy(x)) + f(x, d(x)]
)
dx
=
1
3
∫ b
a
hy(x)f(x, c(x))dx +
4
3
∫ b
a
hy(x)f(x, c(x) + hy(x))dx +
1
3
∫ b
a
hy(x)f(x, d(x))dx
=
1
3
I1 +
4
3
I2 +
1
3
I3,
(79)
where
I1 =
∫ b
a
hy(x)f(x, c(x))dx =
hx
3
[
hy(a)f(a, c(a)) + 4hy(
a + b
2
)f(
a + b
2
, c(
a + b
2
)) + hy(b)f(b, c(b))
]
, (80)
I2 =
∫ b
a
hy(x)f(x, c(x) + hy(x))dx
=
hx
3
[
hy(a)f (a, c(a) + hy(a)) + 4hy(
a + b
2
)f(
a + b
2
, c(
a + b
2
) + hy(
a + b
2
)) + hy(b)f(b, c(b) + hy(b))
]
,
(81)
I3 =
∫ b
a
hy(x)f(x, d(x))dx =
hx
3
[
hy(a)f(a, d(a)) + 4hy(
a + b
2
)f(
a + b
2
, d(
a + b
2
)) + hy(b)f(b, d(b))
]
. (82)
15
Completely analogously to double integrals we can compute triple integrals or higher dimensions. However, when
the dimensionality of the space is very high it becomes advantageous to employ Monte-Carlo methods to perform
integration.
IV. MONTE CARLO INTEGRATION
Monte Carlo integration is based on a statistical interpretation of an integral. In particular when we have a
probability distribution function g(x) of a random variable (or set of variables) x, the average value of a quantity f(x)
that depends on the random variable x is given by
〈f(x)〉 =

f(x)g(x)dx∫
gdx
(83)
If we assume a uniform distribution function g(x) = c, then the last expression becomes
〈f(x)〉 =

f(x)dx∫
dx
=

f(x)dx
V
, (84)
where V is the “volume” of the integration domain. This last equation implies that the integral of a function in a
given domain V is given by
I =

V
f(x)dx = V 〈f(x)〉 (85)
Hence, the integral I equal the product of the volume of the integration domain times the average of f assuming a
uniformly distributed “random” variable x. Thus, in a statistical sense we have
I =

V
f(x)dx = V
N∑
i=1
f(xi)
N
, N →∞ (86)
The central limit theorem guarantees that the error in computing the integral in this way goes down as 1/

N
independently of the dimensionality of the integral. But, e.g., in Simpson’s method the error goes down as h4 ∼ 1/n4,
where n is the number of points in each direction of a multi-D integral. The total number of points however is
N = nd, where d is the dimensionality of the integral. Thus, error scales as ∼ 1/N4/d. If we want to determine the
dimensionality at which the Monte-Carlo method becomes advantageous we simply equate the two errors, i.e.,
1
N4/d
∼ 1
N1/2
⇒ d ∼ 8. (87)
Thus, for d & 8 the Monte-Carlo method becomes more efficient than standard numerical methods.
Algorithm for Monte-Carlo method: Goal to approximate I =
∫ b
a
f(x)dx
1. INPUT: f(x), end points a, b and a large positive integer n.
OUTPUT: approximation to I.
2. Set V = (b− a).
3. Set xI = 0 (sum over i)
4. For i = 1, . . . N do steps 5 and 6.
5. Generate a random number x in the interval [a, b] and compute f(x). In C++ (using the C++ 2011 standard)
generating random numbers can be achieved as follows
#include
#include
#include
#include
using namespace std;
16
int main()
{
double a=0.;
double b=1.;
double lower_bound = a;
double upper_bound = b;
// Seed with a real random value, if available
std::random_device rd;
std::mt19937 gen(rd()); // seed
std::uniform_real_distribution unif(lower_bound,upper_bound); //uniform distribution between -1 and 1
// Choose a random mean between a and b
for (int i=0;i<100;i++){
double a_random_double = unif(gen); //a_random_double is the random number you desire
cout << setprecision(16) << a_random_double << endl;
}
return 0;
}
Note: To compile the code in the C++ 2011 standard you need to add -std=c++11 in the
compilation flag, i.e., g++ -std=c++11.
6. Set xI = xI + f(x)
7. Set xI = V ∗ xI/N .
8. Return (xI)
Exercize 5: Use the Monte Carlo method to compute the integral I =
∫ 1
−1 cos(x)dx. Run your code
for N = 102,104,106,108. Plot the relative error as a function of N. Does it scale roughly as 1/

N?
We point our here, that there are smarter ways to sample a function, rather than the uniform sampling discussed
here. By sampling the function where the contribution to the integral is larger leads to faster convergence of the
numerical integration. However, we will not be discussing other sampling methods. Also, there exist better random
number generators than the intrinsic C++ one.
MTH6150: Ordinary differential equations (ODEs)
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
In this note we discuss methods for numerically solving ODEs.
I. ODES IN PHYSICS
Physics equations of motion require the solution of ordinary differential equations. While the vast majority of
equations in Physics are partial differential equations, ordinary differential equations pop-up in scenarios where a high
degree of symmetry and/or stationarity (time-independence) is imposed (e.g., stationary and spherically symmetric).
For example, in Newtonian gravity the equation for the gravitational potential is
∇2Φ = 4πGρ, (1)
but in spherical symmetry this becomes an ODE
1
r2
d
dr
(
r2
d
dr
Φ
)
= 4πGρ. (2)
The Newtonian equation of motion is also a second-order ODE in time (acceleration=force/mass),
d2x
dt2
= F/m (3)
For a very complex density distribution or complicated (time-dependent) forces, these last equations are not easily
solved analytically. So, in such cases we need to solve the ODEs numerically. There are multiple ways one can
numerically solve ODEs, and we will review here some of these methods including the Euler, and Runge-Kutta
methods. We will also discuss the distinction between initial value and boundary value problems.
II. INITIAL VALUE PROBLEMS: THEORETICAL BACKGROUND
An initial value problem (IVP) for ODEs is described by the following equations
dy
dt
= f(t, y), a ≤ t ≤ b, Initial conditions: y(a) = α (4)
for a single ODE, and
d~y
dt
= ~F (t, y), a ≤ t ≤ b, Initial conditions: ~y(a) = ~α, (5)
where
~y = (y1, y2, . . . yn), ~F = (f1(t, ~y), f2(t, ~y) . . . fn(t, ~y)). (6)
for a system of coupled ODEs.
Before we decide to code any differential equation in a computer it is always important to be able to answer the
following questions: 1) Does the ODE have a solution? 2) Is the ODE solution unique for a given initial conditions?
3) Does the solution depend continuously on the initial conditions? All three questions point to the concept of well-
posedness of the IVP. If the answer is NO to any of these 3 questions, the IVP is not well-posed, and it is redundant
to try to solve the ODE as an IVP, as the numerical algorithm will fail. In this first section we will introduce the
mathematical background for answering the above questions.
DEFINITIONS
2t1, y1
t2, y2
t1, y1
t2, y2
FIG. 1. Left: depiction of a convex set. Right: depiction of a non-convex set.
1. We say that f(t, y) satisfies a Lipschitz condition (or is Lipschitz continuous) in the variable y on a set D ⊂ R2,
if a constant L > 0 exists with |f(t, y1)− f(t, y2)| ≤ L|y1 − y2|.
For example, f(t, y) = ty, D = {(t, y)|1 ≤ t ≤ 2,−3 ≤ y ≤ 4}, then for (t, y1), (t, y2) we have |f(t, y1)−f(t, y2)| =
|t||y1 − y2| ≤ 2|y1 − y2|. Thus, L = 2 and f(t, y) = ty is Lipschitz continuous.
2. A set D ⊂ R is called convex, if for (t1, y1) and (t2, y2) ∈ D, then [(1−λ)t1 +λt2, (1−λ)y1 +λy2] ∈ D ∀λ ∈ [0, 1]
Theorem 1: If f(t, y) is defined on a convex set D ⊂ R2, and there exists a postive L > 0 such that
∣∣∣∣∂f∂y (t, y)∣∣∣∣ ≤ L
∀(t, y) ∈ D, then f is Lipschitz continuous on D, with Lipschitz constant L.
Theorem 2: If D = {(t, y) | a ≤ t ≤ b,−∞ < y < ∞}, and f(t, y) is continuous on D, and f(t, y) satisfies a Lipschitz
condition on D in y, then the IVP
dy
dt
= f(t, y), a ≤ t ≤ b, Initial conditions: y(a) = α (7)
has a unique solution y(t) for a ≤ t ≤ b.
For example, the IVP
dy
dt
= f(t, y) = 1 + t sin(ty), 0 ≤ t ≤ 2, Initial conditions: y(a) = α (8)
satisfies
∂f
∂y
= t2 cos(ty) ≤ 4, (9)
Thus, by theorems 1 and 2, the IVP with f(t, y) = 1 + t sin(ty) has a unique solution.
Well-posedness of the IVP
The IVP
dy
dt
= f(t, y), a ≤ t ≤ b, Initial conditions: y(a) = α (10)
is well-posed if
• A unique solution y(t) exists
• There exist constants ²0 > 0, k > 0 such that ∀² > 0 with ²0 > ² > 0, whenever δ(t) is continuous with |δ(t)| < ²
∀t ∈ [a, b], and δ0 < ², the IVP
dz
dt
= f(t, z) + δ(t), a ≤ t ≤ b, Initial conditions: z(a) = α + δ0 (11)
has a unique solution z(t) that satisfies
|z(t)− y(t)| < k²0, ∀t ∈ [a, b]. (12)
3Theorem 3: Assume D = {(t, y) | a ≤ t ≤ b,−∞ < y < ∞}, where f(t, y) is continuous and is Lipschitz continuous
in y on D, then the IVP
dy
dt
= f(t, y), a ≤ t ≤ b, Initial conditions: y(a) = α (13)
is well-posed.
Well-posedness is a fundamental property of initial value problems for ordinary and partial differential equations.
If a differential equation does not admit a well-posed initial value problem, then we cannot integrate it numerically.
This is because, well-posedness is intimately related with the “stability” of the solution as stated in Eq. ( 12). This
equation implies that the perturbed initial value problem must have a solution that is very close to the unperturbed
one, i.e., the absolute difference between the solutions z(t) and y(t) is bounded. If a small perturbation either in
initial conditions or in the right-hand-side of Eq. (11) leads the difference between the solution z(t) and y(t) to grow
without bound, then this problem is ill-posed and cannot be integrated in a computer. The reason is that computers
will always have finite error, i.e., will always apply small perturbations in the form of either truncation or round-off
error. More specifically well-posedness implies continuous dependence on the initial data.
III. INITIAL VALUE PROBLEMS: NUMERICAL ALGORITHMS
We will now investigate numerical algorithms for solving IVPs for ODEs using first-order and higher-order accurate
methods.
A. Euler’s Method
For an IVP
dy
dt
= f(t, y), a ≤ t ≤ b, y(a) = α (14)
We seek a solution y(t) at various values of t ∈ [a, b] called the mesh points. The solution at other t can be obtained
by interpolation. Consider the following mesh points
ti = a + ih, i = 0, 1, 2, . . . , N, (15)
where h = (b−a)/N = ti+1− ti is called the step size. The simplest possible method for solving an IVP is the forward
Euler method, which is derived from Taylor’s theorem
y(ti+1) = y(ti) + h
dy
dt
∣∣∣∣
ti
+
h2
2
y′′(ξ) ⇒ (16)
y(ti+1) = y(ti) + hf(ti, y(ti)) +
h2
2
y′′(ξ) (17)
Dropping the 2nd-order term, we have then the following algorithm
y0 =α
yn+1 =yn + hf(tn, yn), n = 0, 1, 2, . . . N − 1. (18)
The last equation is called a difference equation, and we denote yn = y(tn).
Exercise 1: Consider the following IVP
dy
dt
= −y, 0 ≤ t ≤ 3, y(0) = 1. (19)
Is the problem well-posed? Use Euler’s method to solve the IVP for N = 100, N = 500, N = 1000, N = 5000, N =
10000 and compare y(3) with the analytic (exact) solution. How quickly does the relative error decay as a function
of h?
4B. Higher-order Taylor methods
Euler is only a first-order accurate method and is not used in practise. There are higher-order accurate methods
that are preferred instead. Before we discuss standard methods we will first overview higher-order Taylor methods.
Consider the IVP
y′ = f(t, y), a ≤ t ≤ b, y(a) = α (20)
where a prime ′ implies differentiation with respect to t. Expand
y(tn+1) = y(tn) + hy′(tn) +
h2
2
y′′(tn) + . . . +
hk
n!
y(k)(tn) +
hk+1
(k + 1)1
y(k+1)(ξn), ξn ∈ (tn, tn+1). (21)
But, from the ODE
y′(t) = f(t, y(t)), y′′(t) = f ′(t, y(t)), . . . y(k)(t) = f (k−1)(t, y(t)) (22)
By use of Eq. (22), Eq. (21) becomes
y(tn+1) = y(tn) + hf(tn, y(tn)) +
h2
2
f ′(tn, y(tn)) + . . . +
hk
n!
f (k−1)(tn, y(tn)) +
hk+1
(k + 1)1
f (k)(ξn, y(ξn)). (23)
Equation (23) motivates the algorithm of the Taylor method of order m, i.e.,
y0 =α
yn+1 =yn + hT (m)(tn, yn), n = 0, 1, . . . N − 1,
(24)
where
T (m)(tn, yn) = f(tn, yn) +
h
2
f ′(tn, yn) + . . . +
h(m−1)
m!
f (m−1)(tn, yn) (25)
Euler is a Taylor method of order 1.
C. Runge-Kutta Methods
Runge-Kutta (RK) methods are widespread, efficient, and almost the standard. We will derive the second-order
accurate RK2 method, and simply present the 3rd (RK3) and 4th-order accurate RK4 methods.
First, let us consider Taylor’s theorem in 2 variables. Let f(t, y) be Cn+1 on D = {(t, y) | a ≤ t ≤ b, c ≤ y ≤ d}
and (t0, y0) ∈ D. Then ∀ (t, y) ∈ D, there exists ξ between (t, t0) and (y, y0) such that
f(t, y) = Pn(t, y) + Rn(t, y), (26)
where the Taylor polynomial of degree n is
Pn(t) =f(t0, y0) +
[
(t− t0)∂f
∂t
(t0, y0) + (y − y0)∂f
∂y
(t0, y0)
]
+
[
(t− t0)2
2
∂2f
∂t2
(t0, y0) + (t− t0)(y − y0) ∂
2f
∂t∂y
(t0, y0) +
(y − y0)2
2
∂2f
∂y2
(t0, y0)
]
+ . . .
[
1
n!
n∑
j=0
(
n
j
)
(t− t0)n−j(y − y0)j ∂
nf
∂tn−1∂yj
(t0, y0)
] (27)
and the remainder is
Rn(t, y) =
1
(n + 1)!
n+1∑
j=0
(
n + 1
j
)
(t− t0)n+1−j(y − y0)j ∂
(n+1)f
∂tn+1−j∂yj
(ξ, μ), (28)
5for some ξ ∈ (t0, t), and μ ∈ (y0, y).
RK2
We wish to find a1, α1, β1 such that a1f(t + α1, y + β1) approximates T (2), i.e.,
T (2)(t, y) = f(t, y) +
h
2
f ′(t, y) (29)
with no error greater than O(h2). Since,
f ′(t, y) =
df
dt
=
∂f
∂t
+
∂f
∂y
dy
dt
, and
dy
dt
= f, (30)
we have
T (2)(t, y) = f(t, y) +
h
2
∂f
∂t
+
h
2
∂f
∂y
∙ f. (31)
We also have from the Taylor expansion in 2 variables
a1f(t + α1, y + β1) = a1f(t, y) + a1α1
∂f
∂t
+ a1β1
∂f
∂y
+ a1 R1(t + a1, y + β1)︸ ︷︷ ︸
residual
(32)
where the residual is O(h2) if 2nd-order derivatives of f are bounded. If we match Eq. (32) (without its residual) to
Eq. (31) we obtain the following set of algebraic equations
a1 =1
a1α1 =
h
2
a1β1 =
h
2
f(t, y)
(33)
that is
a1 = 1, α1 =
h
2
, β1 =
h
2
f(t, y). (34)
Thus,
T (2)(t, y) = f
(
t +
h
2
, y +
h
2
f(t, y)
)
−R1 (35)
We have therefore arrived at the RK2 algorithm
y0 =α
yn+1 =yn + hf
(
tn +
h
2
, yn +
h
2
f(tn, yn)
)
, n = 0, 1, . . . N − 1, (36)
To avoid the nested functioning we can rewrite this as follows
y0 =α
k1 =hf(tn, yn)
k2 =hf(tn +
h
2
, yn +
1
2
k1)
yn+1 =yn + k2, n = 0, 1, . . . N − 1,
(37)
RK3
6The RK3 (Heun’s) algorithm is the following
y0 =α
k1 =hf(tn, yn)
k2 =hf(tn +
h
3
, yn +
1
3
k1)
k3 =hf(tn +
2h
3
, yn +
2
3
k2)
yn+1 =yn +
1
4
(k1 + 3k3)
(38)
for each n = 0, 1, . . . N − 1. The method has local truncation error O(h3), as long as y(t) is at least C4.
RK4
The RK4 algorithm is the following
y0 =α
k1 =hf(tn, yn)
k2 =hf(tn +
h
2
, yn +
1
2
k1)
k3 =hf(tn +
h
2
, yn +
1
2
k2)
k4 =hf(tn+1, yn + k3)
yn+1 =yn +
1
6
(k1 + 2k2 + 2k3 + k4)
(39)
for each n = 0, 1, . . . N − 1. The method has local truncation error O(h4), as long as y(t) is at least C5.
Algorithm for RK4:
Input: a, b, N , initial condition α, f(t, y)
• Step 1: Set h = (b− a)/N
t = a
y = α
output(t,y)
• Step 2: For n = 1, 2, . . . N do Steps 3-5
• Step 3:
k1 =hf(t, y)
k2 =hf(t + h/2, y + k1/2)
k3 =hf(t + h/2, y + k2/2)
k4 =hf(t + h, y + k3)
(40)
• Step 4:
y = y + (k1 + 2k2 + 2k3 + k4)/6 (41)
t = a + n ∙ h (42)
• Step 5:
output(t,y)
7Exercise 2: Consider the following IVP
dy
dt
= −2ty, 0 ≤ t ≤ 2, y(0) = 1. (43)
Solve the ODE analytically. Follow the RK4 algorithm to implement instead the RK2 method and solve the IVP for
N = 100, N = 200, N = 400, N = 800, N = 1600 and compare y(3) with the analytic (exact) solution and the Euler
solution. How quickly does the relative error decay as a function of h for RK2? Is RK2 more accurate than Euler?
To make the code modular define a function for the right-hand-side of the equation, i.e., f(t, y). Define a function
RK2 that does the integration from t to t + h and to which you will pass a function pointer that will point to f(t, y).
This way if you change f(t, y), the code is invariant. The RK2 function will take as input t, h, the previous value of
y, the pointer to the function and will return the new value of y, at t + h.
D. Higher order ODEs and systems of ODEs
Many ODEs in physics are of higher than first-order for which the methods we discussed above apply. For example,
a simple harmonic oscillator satisfies
m
d2x
dt2
= −kx (44)
Any higher order ODE which can be solved for the highest order derivative can be written as a system of first order
ODEs. For example, to obtain a first-order form for Eq. (44) we let y = dx/dt, and then the equation becomes the
system of two first-order ODEs for the functions y(t) and x(t)
dx
dt
=y
dy
dt
=− k
m
x
(45)
Thus, a large set of higher-order ODEs can be cast to first-order form as a system of first-order ODEs. This is why
we will discuss here how we can solve numerical systems of coupled first-order ODEs. Thus, we will here concern
ourselves primarily with generalizing our methods to systems of first order ODEs. The IVP for a general first-order
system of m equations can be written as
d~u
dt
= ~F (t, ~u), a ≤ t ≤ b, ~u(t = a) = ~α, (46)
where the column vector of unknown functions ~u(t) = (u1(t), u2(t), u3(t), . . . um(t)), the column vector of “sources”
~F (t, ~u) = (f1(t, u1, u2, . . . , un), f2(t, u1, u2, . . . , un), . . . , fm(t, u1, u2, . . . , un))., and the m initial values ~α = (α1, α2, . . . , αm.
The goal is to find ~u(t). First, we need to generalize our definitions for Lipschitz condition and our theorems for
uniqueness of solutions.
Definition: For a function f(t, u1, . . . , um) defined on D = {(t, u1, . . . , um) | a ≤ t ≤ b, −∞ < ui < ∞, i =
1, 2, . . .m}, we say that f satisfies a Lipschitz condition on D in the variables u1, . . . , um, if there exists a constant
L > 0 such that
|f(t, u1, . . . um)− f(t, z1, . . . zm)| ≤
m∑
i=1
|ui − zj |, for all (t, u1, . . . , um) and (t, z1, . . . , zm) in D. (47)
By virtue of the mean value theorem it can be shown that if f and its first partial derivatives are continuous in D,
and if ∣∣∣∣∂f(t, u1, u2, . . . um)∂(u1, u2, . . . um)
∣∣∣∣ ≤ L, for i = 1, 2, . . . ,m and all (t, ui) ∈ D, (48)
then f is Lipschitz continuous on D.
Theorem:
Let fi(t, u1, . . . um), i = 1, . . .m be defined on D = {(t, u1, . . . , um) | a ≤ t ≤ b, −∞ < ui < ∞, i = 1, 2, . . .m}. If all
fi(t, u1, . . . um) are Lipschitz continuous on D, then the IVP of Eq. (46) has a unique solution u1(t), u2(t), . . . um(t)
for a ≤ t ≤ b.
81. RK4 for systems of ODEs
Here we will simply generalize RK4 for systems of ODEs of the type of Eq. (46). Let N > 0, such that h = (b−a)/N
is the integration step size. As usually we will define the mesh points
tn = a + n ∗ h, n = 0, 1, 2, . . . N. (49)
We will use the notation ui,n to designate the function i at time tn, i.e., ui,n = ui(tn) We have the initial conditions
u1,0 = α1, u2,0 = α2, . . . , um,0 = αm, (50)
The RK4 algorithm then becomes as follows.
Algorithm for RK4:
Input: a, b, N , initial condition α, f(t, y)
• Step 1: Set h = (b− a)/N
t = a
u1 = α1, u2 = α2, . . . , um = αm,
output(t,ui)
• Step 2: For n = 1, 2, . . . N − 1 do Steps 3-4
• Step 3:
For i = 1, 2, . . . ,m
k1,i =hfi(tn, u1, . . . , um)
For i = 1, 2, . . . ,m
k2,i =hfi(t +
h
2
, u1 +
k1,1
2
, u2 +
k1,2
2
, . . . um +
k1,m
2
)
For i = 1, 2, . . . ,m
k3,i =hfi(t +
h
2
, u1 +
k2,1
2
, u2 +
k2,2
2
, . . . um +
k2,m
2
)
For i = 1, 2, . . . ,m
k4,i =hfi(t + h, u1 + k3,1, u2 + k3,2, . . . um + k3,m)
For i = 1, 2, . . . ,m
ui =ui + (k1,i + 2k2,i + 2k3,i + k4,i)/6
(51)
• Step 4:
t = a + n ∙ h (52)
• Step 5:
output(t,~u)
Exercise 3: Generalize the RK3 algorithm to a system of m first-order ODEs.
Exercise 4: Non-dimensionalize the ODE of the simple harmonic oscillator, and cast it to first-order form.
mx¨ = −k(x− x0). (53)
To achieve a general solution (independent of m and k) we want to non-dimensionalize the equation first. To achieve
this let us perform a dimensional analysis. We will denote the unit of time by [T ], and the unit of length [L]. Then
above equation becomes
m
[L]
[T ]2
= −k[L] ⇒ m 1
[T ]2
= −k ⇒

m
k
= [T ], (54)
9in other words,

m
k defines a timescale in our problem. So, we will introduce a new dimensionless time tˆ = t/

m
k .
Then, Eq. (53) becomes
d2x
dtˆ2
= −x. (55)
We can further introduce the length of the spring at the relaxed position x0 as a lengtscale in the problem to non-
dimensionalize x, i.e., xˆ = x/x0. The fully non-dimensional ODE then becomes
d2xˆ
dtˆ2
= −(xˆ− 1). (56)
The benefit of this form is that all we need is to solve this problem once, and then by choosing x0 and

m
k we can
rescale our time and length to solve the problem for any value of x0 and

m
k (for our choice of initial conditions).
Since we can rename our variables, we can remove the hats to obtain the final non-dimensional form of the harmonic
oscillator
d2x
dt2
= −(x− 1). (57)
E. Stability of numerical schemes for ODE IVP
We have so far seen that round-off error instabilities can arise that destroy the stability of a numerical integration
scheme. Numerical schemes for ODEs can also be unstable, and they have to be analyzed for each ODE separately.
Let us consider for example the simple IVP
du
dt
= −λu, u(0) = u0, t > 0. (58)
and let us consider the forward Euler method as the solution scheme, i.e.
un+1 = un − λunh = (1− λh)un (59)
Let us now consider that we have an exact solution of the algebraic Eqs. (59), un, and that we perturbed it un →
un + δun (essentially the perturbation is seeded by round off error), then the above equation becomes
δun+1 = (1− λh)δun (60)
which we can rewrite as
δun+1 = (1− λh)nδu0 (61)
from which we can write ∣∣∣∣δun+1δu0
∣∣∣∣ = |1− λh|n (62)
Thus, the term |1− λh| is an amplification factor. If |1− λh|> 1, then the magnitude of the perturabation grows in
time, if |1− λh|≤ 1, the perturbation does not grow. Thus, we have stability if
|1− λh|≤⇒ −1 ≤ 1− λh ≤ 1 (63)
The right side of the inequality is always satisfied for λ > 0 (h is defined to be > 0), but the left side of the inequality is
satisfied only if h ≤ 2/λ! Thus, if the step size is greater than 2/λ, the forward Euler method is numerically unstable!
Let us know consider the RK2 algorithm for the same ODE (58), for which we have f(t, u) = −λu. The RK2
algorithm is
un+1 = un + hf
(
tn +
h
2
, un +
h
2
f(tn, un)
)
(64)
10
Using f(t, u) = −λu, we can rewrite this as
un+1 = un + hf
(
tn +
h
2
, un − λh2un)
)
(65)
and
un+1 = un − λh(un − λh2un) = un
(
1− λh(1− λh
2
)
)
(66)
or
un+1 = u0
(
1− λh + (λh)
2
2
)n
(67)
and again we obtain ∣∣∣∣un+1u0
∣∣∣∣ = ∣∣∣∣1− λh + (λh)22
∣∣∣∣n (68)
with the amplification factor this time being
∣∣∣∣1− λh + (λh)22 ∣∣∣∣, which for stability must satisfy∣∣∣∣1− λh + (λh)22
∣∣∣∣ ≤ 1. (69)
or
−1 ≤ 1− λh + (λh)
2
2
≤ 1. (70)
The right inequality becomes
λh(
λh
2
− 1) ≤ 0 ⇒ λh
2
≤ 1 ⇒ h ≤ 2
λ
, (71)
which is the same restriction on the step size as in the Euler method. The left inequality in (70) requires
0 ≤ 2− λh + (λh)
2
2
(72)
If we set x = λh, the right-hand-side in the last equation becomes 12x
2 − x + 2, whose discriminant is Δ = 1 − 4 122 =
−3 < 0. Thus, 12x2− x + 2 > 0. Hence, inequality (72) is always satisfied. Thus, RK2 has the same restriction on the
timestep as the Euler method, i.e.,
h ≤ 2
λ
. (73)
Thus, we cannot choose an arbitrarily large h, because the numerical scheme will become unstable!
As a rule of thumb, when solving IVP usually there is an associated time (or length) scale with the problem, that
we must resolve, i.e, the step must be smaller than this scale. The ODE (58), has as characteristic time scale 1/λ.
Thus, to be numerically stable (and to have an accurate solution), we must resolve this time scale, i.e.,
h < 1/λ, (74)
which is a restriction very close to the one we obtained with a much more elaborate perturbation analysis. So, always
try to think about the problem you are trying to solve. Determine the characteristic scales, and these will determine
what step size you should use. Thinking, can save us a lot of time from trying to figure out why our codes may
fail/crash.
Exercise 5: Let us now consider a system of ODEs
dx
dt
=− 2
3
(x + y)
dy
dt
=
1
3
(2x− 7y)
(75)
11
What is the numerical stability condition when employing the forward Euler method?
Solution: The forward Euler method becomes
xn+1 =xn − 23(xn + yn)h
yn+1 =yn +
1
3
(2xn − 7yn)h
(76)
Let consider Harmonic perturbations to our variables, i.e.
xn = x0 exp(inh), yn = y0 exp(inh), (77)
where i is the imaginary unit. If we plug Eq. (77) in Eq. (78) we obtain
x0 exp(ih) =x0 − 23(x0 + y0)h
y0 exp(ih) =y0 +
1
3
(2x0 − 7y0)h
(78)
This is the following eigenvalue problem[
1− 2h/3 −2h/3
2h/3 1− 7h/3
] [
x0
y0
]
= λ
[
x0
y0
]
(79)
where λ = exp(ih) is the eigenvalue. For the system to be numerically stable the eigen values of the matrix
M =
[
1− 2h/3 −2h/3
2h/3 1− 7h/3
]
(80)
must be smaller than unity. To find the eigenvalue of M we need to solve the characteristic polynomial
det|M − λI| = 0 ⇒ (1− 2h/3− λ)(1− 7h/3− λ) + 4h2/9 = 0. (81)
This equation has the following solutions λ1 = 1− h, λ2 = 1− 2h. For the numerical integration system to be stable
the absolute value of both eigenvalues must be smaller than unity, i.e., we obtain the following stability requirements
|1− h| < 1, and |1− 2h| < 1, which yield −1 < 1− h < 1 and −1 < 1− 2h < 1, and h < 2 and h < 1/2, respectively.
Exercise 6: Implement the RK4 algorithm for the following system of ODEs
dx
dt
=− 2
3
(x + y)
dy
dt
=
1
3
(2x− 7y)
(82)
This system is exactly solvable. The help you compare your algorithm the analytic solution of the system is the
following
x =
2
3
e−t (2x0 − y0)− 13e
−2t (x0 − 2y0)
y =
1
3
e−t (2x0 − y0)− 23e
−2t (x0 − 2y0)
(83)
where x0 = x(0) and y0 = y(0) are the initial conditions.
IV. BOUNDARY VALUE PROBLEMS AND STIFF ODES
In many occasions in physics we need to specify not only an initial condition for the ODE, but also a “final”
condition. These types of problems are called boundary value problems. A first-order ODE cannot have a boundary
value problem because all first-order ODEs require is the value of the variable at one point, e.g., the value at one
“boundary”. Thus, second or higher order ODEs are the only ones that can have boundary value problems. For
example, consider the electrostatic potential between two metal spheres of different radii. We can ask what is the
potential between the two if the potential of the inner sphere is kept at potential Vinner and the outer one sphere is
12
kept at potential 0. The potential between the sphere is governed by Laplace’s equation, which in spherical symmetry
reduces to
d2V
dr2
+
2
r
dV
dr
= 0, V (Rinner =) = Vinner, V (Router) = 0. (84)
The reason why higher order systems can be cast as boundary value problems is that a higher order system requires
more than one initial condition to determine the solution. In the example above, the second order equation requires
that we know the value of V and the value of dV/dr at a point to solve it completely. Thus, it requires two conditions.
Instead of providing the derivative, one can provide the value of the function at another point, making it a boundary
value problem.
The above discussion should have already prepared the ground for one of the most common methods for solving
boundary value problems – the shooting method. In the shooting method one casts the boundary value problem as
an initial value problem where one would set V (Rinner =) = Vinner, and then set the derivative to some value solve
the initial value problem, and if V (Router = 0) 6= 0, go back change the value of the derivative and iterate until
V (Router = 0) = 0, to within a certain tolerance. This can be done for linear and non-linear equations, but we will
not expand further on this topic here.
In addition to boundary value problems there exist stiff ordinary differential equations, which typically have part
of their solution a transient solution (signal) changing on a rapid time scale, and a more “stready-state” part which
changes on a slower time scale. These problems require special numerical schemes to be solved. We only mention stiff
systems here, in case you ever run into a problem that has such behavior, and you need to resort to these special stiff
solvers.
MTH6150: Partial differential equations (PDEs)
Charalampos Markakis1 & Vasileios Paschalidis2
1 School of Mathematical Sciences, Queen Mary University of London and
2 Departments of Astronomy & Physics, University of Arizona, Tucson
In this note we discuss methods for numerically solving PDEs.
I. PDES IN PHYSICS
The vast majority of equations describing physical theories are partial differential equations, For example, in
Newtonian gravity the equation for the gravitational potential is
∇2Φ = 4πGρ, (1)
The propagation of a disturbance on a string, or electromagnetic perturbations satisfy a wave equation
∂u
∂t
= c2∇2u, (2)
where c is the speed of propgation of information.
Inhomogeneities in temperature T in a medium obey the heat equation
∂T
∂t
= κ∇2T, (3)
where κ > 0 is the diffusivity. Additional examples involve the Euler equations of (magneto)hydrodynamics, Einstein’s
theory of general relativity, and many more.
II. THEORETICAL BACKGROUND
Any course on numerically solving PDEs must start with the classification of the equations.
A. Types of PDEs based on second-order equations in two dimensions
The classification of PDEs is extremely important, because it dictates the numerical methods that one can adopt
to solve the equations. We will start our classification of PDEs by considering the most general second-order PDE in
two variables and constant coefficients
A
∂2u
∂x2
+ B
∂2u
∂x∂y
+ C
∂2u
∂y2
+ D
∂u
∂x
+ E
∂u
∂y
+ F = 0. (4)
This equation is classified based on the discriminant Δ = B2 − 4AC.
• If Δ > 0, the equation is of the hyperbolic type.
• If Δ = 0, the equation is of the parabolic type.
• If Δ < 0, the equation is of the elliptic type.
Thus, the classification of PDEs is based solely on the highest derivative operators.
We will later give general definitions of hyperbolic, parabolic and elliptic PDEs in any number of dimensions. For
now let us consider a few examples.
In Cartesian coordinates Poisson’s equation in 2D is written as
∂2Φ
∂x2
+
∂2Φ
∂y2
= 4πGρ. (5)
2This matches Eq. (4) for A = 1, B = 0, C = 1, D = E = 0, F = −4πGρ. This implies that Δ = B2− 4AC = −4 < 0.
Thus, Poisson’s equation is the archetypical example of an elliptic equation.
In Cartesian coordinates the wave equation in one spatial dimension is written as
∂2u
∂t2
− c2 ∂
2u
∂x2
= 0 (6)
If we identify t with y, this matches Eq. (4) for A = −c2, B = 0, C = 1. This implies that Δ = B2 − 4AC = 4c2 > 0.
Thus, the wave equation is the archetypical example of a hyperbolic equation.
In Cartesian coordinates the heat equation in one spatial dimension is written as
∂T
∂t
− κ∂
2T
∂x2
= 0 (7)
If we identify t with y, this matches Eq. (4) for A = κ > 0, B = 0, C = 0, D = 0, E = 1, F = 0. This implies that
Δ = B2− 4AC = 0. Thus, the heat equation is the archetypical example of a parabolic equation. Note that although
the highest derivative operator determines the classification of the equation, it is the fact that E 6= 0 that makes
the equation parabolic. If the only non-zero coefficient was A then the equation would be an ordinary differential
equation.
One of the most fundamental physical differences between hyperbolic, parabolic, and elliptic PDEs is that among
them only hyperbolic equations have finite speed of propagation of information, which is given by the wave speed c.
Elliptic equations have infinite speed of progation of information, this can be handwaivingly be understood by
writing the wave equation as
∇2u = 1
c2
∂2u
∂t2
, (8)
which for c →∞ becomes ∇u = 0, which is an elliptic equation.
Parabolic PDEs also have infinite speed of propagation of information, but also affect different scales (wavelengths)
in different ways. We will later describe in more detail these properties. The fact that only hyperbolic equations
have finite speed of propagation of information, implies that among the three types, only hyperbolic equations can be
causal, and respect the fact that the maximum speed of propagation of information is the speed of light in vacuum.
Apart from these physical differences the different PDE types require different numerical methods for solving them.
Hyperbolic and parabolic PDEs can be solved as initial value problems, but elliptic PDEs can be solved only as
boundary value problems.
It is important to note that in general PDE does not have to be of a single type. In particular, a PDE or system
of PDEs can change type. A typical example is the following PDE
∂2u
∂t2
= −t∇2u (9)
This equation is hyperbolic for t < 0, but becomes elliptic for t > 0 and degenerate parabolic for t = 0. In addition,
a system of PDEs can be of mixed type: we can have mixed hyperbolic-elliptic and mixed hyperbolic-parabolic (e.g.
the Navier-Stokes equations). In fact, one can possibly imagine mixed hyperbolic-parabolic-elliptic systems of PDEs.
Finally, there can be PDEs that cannot be chatacterized using the above classification scheme.
B. PDE types and systems of PDEs in any dimensions
The world around us is 3-dimensional and equations in physical theories can also be in four of higher numbers of
dimensions. Thus, to understand what numerical methods to employ for solving complex high-order PDEs or systems
of PDEs we would like to be able to classify PDEs in any number of dimensions and in systems. Since, most PDEs
in Physics are of second-order let us start with the classification of second-order PDEs in any number of dimensions.
1. Classification of second-order PDEs in d dimensions
A general second-order PDE with constant coefficients in d dimensions can be written as follows
d∑
i=1
d∑
j=1
Aij
∂2u
∂xi∂xj
+ lower order terms = 0. (10)
The PDE type is entirely determined by the matrix Aij .
3• If the matrix is positive (or negative) definite, i.e.,
d∑
i=1
d∑
j=1
Aijk
ikj > 0, (11)
for any d-dimensional vector ~k, then the PDE is elliptic. Since, a matrix is positive (or negative) definite when
all its eigenvalues are positive (or negative), then the PDE is elliptic if all eigenvalues of Aij are positive (or all
are negative).
• If all, but one of the eigenvalues of Aij are positive (or negative), and one eigenvalue is 0, then the PDE is
parabolic.
• If Aij has one negative (or positive) eigenvalue, and all other eigenvalues are positive (or negative), then the
PDE is hyperbolic.
• If Aij has more than one negative and more than one positive eigenvalue, and there are no zero eigenvalues,
then the PDE is ultrahyperbolic.
Before we continue to systems of PDEs, let us consider a few examples. In Cartesian coordinates Poisson’s equation
in 3D is written as
∂2Φ
∂x2
+
∂2Φ
∂y2
+
∂2Φ
∂z2
= 4πGρ. (12)
The matrix Aij for this PDE is A = diag(1, 1, 1), the Eigenvalues are all therefore λi = 1 > 0, i = 1, 2, 3, and hence
the equation is elliptic. This is true for any number of dimensions.
In Cartesian coordinates the wave equation in 3 spatial dimensions is written as
−∂
2u
∂t2
+ c2(
∂2u
∂x2
+
∂2u
∂y2
+
∂2u
∂z2
) = 0 (13)
The matrix Aij for this PDE is A = diag(−1, c2, c2, c2), the Eigenvalues are λ1 = −1 < 0 and λi = c2 > 0, i = 2, 3, 4.
There is only one negative eigenvalue and all others are positive, hence the equation is hyperbolic. This is true for
any number of spatial dimensions.
In Cartesian coordinates the heat equation in 3 spatial dimensions is written as
∂T
∂t
− κ(∂
2T
∂x2
+
∂2T
∂y2
+
∂2T
∂z2
) = 0. (14)
Since we have a first-order partial derivative with respect to t, but no second-order derivative with respect to t, the
matrix Aij for this PDE is A = diag(0, κ, κ, κ), the Eigenvalues are therefore λ1 = 0 and λi = κ > 0, i = 2, 3, 4.
There is only one zero eigenvalue, and all others are positive, hence the equation is parabolic. This is true for any
number of spatial dimensions.
An example of an ultrahyperbolic equation would be
−∂
2u
∂t2
− ∂
2u
∂v2
+ c2(
∂2u
∂x2
+
∂2u
∂y2
+
∂2u
∂z2
) = 0 (15)
where A = diag(−1,−1, c2, c2, c2), whose eigenvalues are λ1 = −1, λ2 = −1, and λi = c2 > 0, i = 3, 4, 5, i.e., we
have two negative and 3 positive eigenvalues. Ultrahyperbolic equations rarely arise in Physics. So, we will not be
considering them further in this class.
2. Classification of first-order PDEs in d dimensions
Higher-order systems of PDEs can often be cast to completely first-order form.
This is the case for elliptic and hyperbolic PDEs, but parabolic PDEs cannot be cast to pure first-order form. Let
us consider for example the wave equation in two spatial dimensions
∂2u
∂t2
= c2
(
∂2u
∂x2
+
∂2u
∂y2
)
(16)
4To cast it to first-order form first introduce a new variable K = ∂u∂t , then the wave equation becomes
∂u
∂t
=K,
∂K
∂t
=c2
(
∂2u
∂x2
+
∂2u
∂y2
) (17)
But, we to cast this to complete first order form we still have reduce the second-order derivatives to first-order
derivative. This can be achieved by introducing two new variables Dx = ∂u∂x and Dy =
∂u
∂y , then the system becomes
∂u
∂t
=K,
∂K
∂t
=c2
(
∂Dx
∂x
+
∂Dy
∂y
) (18)
But, now the system has 4 variables u,K,Dx, Dy and 2 equations. Thus, it is underdetermined. To close the system
we must promote the variables Dx, Dy to dynamical (evolved variables). This can be achieved by taking a time
derivative using their definition and commuting the time and spatial derivative. For example, from Dx = ∂u∂x we have
∂Dx
∂t
=

∂t
∂u
∂x
=

∂x
∂u
∂t
∂u
∂t =K︷ ︸︸ ︷
=====⇒ ∂Dx
∂t
=
∂K
∂x
. (19)
Similarly it can be shown that
∂Dy
∂t
=
∂K
∂y
. (20)
Thus, the complete reduction of the wave equation to first-order form is
∂u
∂t
=K,
∂K
∂t
=c2
(
∂Dx
∂x
+
∂Dy
∂y
)
,
∂Dx
∂t
=
∂K
∂x
,
∂Dy
∂t
=
∂K
∂y
,
(21)
which has 4 equations for the 4 unknown variables, but is subject to two constraints
Dx − ∂u
∂x
= 0, Dy − ∂u
∂y
= 0. (22)
The system (21) has 4 degrees of freedom and as such the totality of solutions of this system is larger than the totality
of solutions of the original wave equation (which has 2 degrees of freedom). The two systems are equivalent, if and
only if the constraints (22) are satisfied.
Given that we can reduce higher-order PDEs to first order, first we will discuss ellipticity and hyperbolicity of
first-order PDEs, and we will consider general parabolic PDEs subsequently.
A general system of m first-order PDE in d dimensions for m unknown dependent variables u can be written as
follows (not all systems of PDEs can be written in this form, but in general equations in Physics can).
∂~u
∂t
=
d∑
k=1
Ak
∂u
∂xk
+ ~B, (23)
where ~u = (u1, u2, . . . um) are the m unknown variables, and the d in number m×m matrices Akij , and the vector ~B
characterize the linear/non-linear nature of the system. In particular we have:
• If Akij and the vector ~B depend at most on ~u and the coordinates (or independent variables) ~x, the system is
called quasilinear.
5• If Akij were to depend on derivatives of ~u, then the system would be classified as genuinely nonlinear.
• If Akij and the vector ~B depend only on the coordinates ~x or are constant, then the system is called linear and
the superposition principle holds.
Equations in physics of tend to be of the quasilinear type. Regarding the classification of the PDEs as elliptic,
hyperbolic, parabolic as in the case of general second-order PDEs, these are determined by the highest derivative
operator appearing. In the context of PDEs that can be written in the form
∂~u
∂t
= L~u + ~B, (24)
where L is some differential operator, the highest-order derivative part of the equations is called the principal part.
The principal part determines the character of the equations and the fundamental physics described by the equations.
In the context of Eq. (23), the operator L equals
L =
d∑
k=1
Ak

∂xk
(25)
and the principal part of the equations is
∑d
k=1 A
k ∂u
∂xk
, because the highest order derivatives that appear are first-
order. We will later see that this is not the case for parabolic PDEs. The matrices appearing in the principal part of
first-order PDEs can be used to find the characteristic matrix of the system which is defined as
M =
d∑
i=1
Aikˆi, (26)
where kˆ is a unit vector in some norm. In Euclidean space the norm of kˆ is ||k|| =
√∑d
i=1 k
2
i . The eigen-structure
of the characteristic matrix M determines the classification of the PDE (23). We have the following cases:
• If there are any eigenvalues of the characteristic matrix M that are purely imaginary, the system of PDEs is
elliptic.
• If all eigenvalues of the characteristic matrix M are purely real, the sytem of PDEs is hyperbolic. For hyperbolic
systems we have additional classifications
1. If a system of PDEs is hyperbolic, and the principal matrix does not have a complete set of eigenvectors,
the system is called weakly hyperbolic
2. If a system of PDEs is hyperbolic, and the principal matrix has a complete set of eigenvectors, the system
is called strongly hyperbolic
3. If the principal matrix of a hyperbolic system of PDEs has all of its eigenvalues distinct, then the system is
called strictly hyperbolic. A matrix which has distinct eigenvalues also has a complete set of eigenvectors,
and hence strictly hyperbolic systems are strongly hyperbolic.
4. If the principal matrix of the system of PDEs is hermitian, i.e., M † = M , then the system is called
symmetric hyperbolic. Hermitian matrices have real eigenvalues and a complete set of eigenvectors. Thus,
symmetric hyperbolic systems are strongly hyperbolic.
In general the existence of complex eigenvalues for the characteristic matrix M implies at least partial elliptic
behavior.
Let us now apply the above to the Poisson equation in 2D in Cartesian coordinates.
∂2Φ
∂x2
+
∂2Φ
∂y2
= 4πGρ. (27)
First, introduce new variables Dx = ∂Φ∂x , Dy =
∂Φ
∂y , then the above equation becomes
∂Φ
∂x
=Dx
∂Dx
∂x
=− ∂Dy
∂y
+ 4πGρ
(28)
6and we need an equation for how Dy varies along x, which we obtain from Dy = ∂Φ∂y as follows
∂Dy
∂x
=

∂x
∂Φ
∂y
=

∂y
∂Φ
∂x
=
∂Dx
∂y
. (29)
Using this last equation the original Poisson equation can be cast to complete first-order form as
∂Φ
∂x
=Dx,
∂Dx
∂x
=− ∂Dy
∂y
+ 4πGρ,
∂Dy
∂x
=
∂Dx
∂y
.
(30)
In the language of Eq. (23) we have the following identifications t → x, x → y, ~u = (Φ, Dx, Dy). Since the right-hand-
side of Eq. (30) is in 1D, there is only one matrix Ai, which is given by
M = Ay =
0 0 00 0 −1
0 1 0
 (31)
Note that since there is only one Ai matrix the characteristic matrix is the same as the Ay matrix. To find the
eigenvalues of this matrix we need to solve the characteristic polynomial, i.e., det |M − λI| = 0, which for this matrix
becomes
λ(λ2 + 1) = 0. (32)
Thus, there are 3 eigenvalues: λ0 = 0, λ1,2 = ±i. Since, there are purely imaginary eigenvalues, this implies that the
system is of elliptic type. We should have anticipated this because the Poisson equation is elliptic.
Exercise 1: What type of system is the one of Eq. (21) when c = 1 (we can always set c = 1 by choise of units)?
3. Generalized parabolic equations
Consider a system of n partial differential equations for n unknown variables ~u of the form
∂~u
∂t
= [P2m
(

∂x
)
+ Q
(

∂x
)
]u, (33)
where P2m is a quasi-linear even-order operator given by
P2m
(

∂x
)
=

ν=2m
Aν(~u)
∂ν1
∂xν11
∂ν2
∂xν22
. . .
∂νd
∂xνdd
(34)
here
∑d
i=1 νi = ν = 2m, d is the number of spatial dimensions, and Q is a quasi-linear operator of order less than 2m
Q
(

∂x
)
=

ν≤2m−1
Bν(~u)
∂ν1
∂xν11
∂ν2
∂xν22
. . .
∂νd
∂xνdd
, (35)
where
∑d
i=1 νi = ν ≤ 2m − 1, and Aν , Bν are n × n matrix functions of the dynamical variables ~u, but not of their
derivatives.
Definition: The equation Eq. (33) is called 2m-order parabolic, if for all k ∈ R, the eigenvalues ωj(k), j = 1, . . . , n
of the n× n matrix
P2m(ik) = −

ν=2m
Aνk
ν1
1 k
ν2
2 . . . k
νd
d , (36)
(in P2m(ik) the symbol i is the imaginary unit i2 = −1) satisfy
Re(ωj(k)) ≤ −δ|k|2m, j = 1, 2, . . . , n, (37)
7with some δ > 0 independent of k.
Let us see the definition in practice. Consider the heat equation in Cartesian coordinates in three spatial dimensions
∂T
∂t
= ξ
(
∂2
∂x2
+
∂2
∂y2
+
∂2
∂z2
)
T, (38)
The even order operator here has m = 1, i.e.,
P2
(

∂x
)
= ξ
(
∂2
∂x2
+
∂2
∂y2
+
∂2
∂z2
)
(39)
Now we have to replace ∂∂x → ikx, ∂∂y → iky, ∂∂z → ikz
P2 (ik) = ξ
(
i2(kx)2 + i2(ky)2 + i2(kz)2
)
= −ξ|k|2. (40)
Thus, if ξ > 0, then for δ > ξ we satisfy the definition of parabolicity
P2 (ik) = ξ
(
i2(kx)2 + i2(ky)2 + i2(kz)2
)
= −ξ|k|2 < −δ|k|2 (41)
But, if ξ < 0 the equation does not satisfy the definition of parabolicity.
Let us now consider a 4th-order system in three spatial dimensions
∂u
∂t
= θ
(
∂4
∂x4
+
∂4
∂y4
+
∂4
∂z4
)
u, (42)
The even order operator here now has m = 2, i.e.,
P4
(

∂x
)
= θ
(
∂4
∂x4
+
∂4
∂y4
+
∂4
∂z4
)
(43)
Now we have to replace ∂∂x → ikx, ∂∂y → iky, ∂∂z → ikz
P4 (ik) = θ
(
i4(kx)4 + i4(ky)4 + i4(kz)4
)
= θ
(
(kx)4 + (ky)4 + (kz)4
)
(44)
Thus, if θ < 0, then since (kx)4 + (ky)4 + (kz)4 <
(
(kx)2 + (ky)2 + (kz)2
)2 = k4 for δ > |θ| we satisfy the definition of
parabolicity
P4 (ik) = θ
(
i2(kx)2 + i2(ky)2 + i2(kz)2
)
= −|θ|
(
(kx)4 + (ky)4 + (kz)4
)
< −δ|k|4 (45)
But, if θ > 0 the equation does not satisfy the definition of parabolicity. For θ < 0, Eq. (42) is called 4th-order
parabolic.
C. Well-posedness of the initial value problem
As in the case of ordinary differential equations, certain types of PDEs can be solved as initial value problems. For
PDEs the initial value problem is also referred to as the Cauchy problem. Given that when we have second-order time
derivatives we will be reducing the system to first-order in time, consider the following initial value problem (IVP) in
d dimensions
∂~u
∂t
= P
(

∂x
)
, a ≤ t ≤ b, Initial conditions: ~u(t = 0, x1, x2, . . . xd) = ~α(x1, x2, . . . xd). (46)
where P is a spatial quasi-linear differential operator. We will not give a precise definition of well-posedness for PDEs,
but we will describe its properties. The IVP (46) is well-posed if
• A solution ~u(t, x1, x2, . . . xd) exists.
8• The solution ~u(t, x1, x2, . . . xd) is unique for given initial data.
• The solution exhibits continuous dependence on initial data.
Theorems in mathematics guarantee the (local) well-posedness of (quasi-linear) linear strongly hy-
perbolic and parabolic PDEs.
Elliptic PDEs do not admit a well-posed IVP. It is instructive to see this. For both the classification and
the well-posedness of the IVP the highest order operator is the most important part of a PDE. Let us consider
the wrong-sign wave equation in Cartesian coordinates, i.e., (contrast the following equation with the correct sign
Eq. (13))
∂2u
∂t2
+ c2(
∂2u
∂x2
+
∂2u
∂y2
+
∂2u
∂z2
) = 0 (47)
The matrix Aij appearing in Eq. (10) here becomes Aij = diag(1, c2, c2, c2), hence its eigenvalues are all positive and
thus the wrong-sign wave equation is elliptic. Let us know consider that we try to solve this equation as an IVP, i.e.,
at t = 0 we provide initial conditions for u(0, x, y, z), and its time derivative ∂u∂t (0, x, y, z). In order for the problem to
be well-posed small perturbation should not grow in time, they should be bounded so that the solution satisfies the
last condition of well-posedness – continuous dependenc on initial data. Thus, let us consider harmonic perturbations
u = u0 exp[i(~k ∙ ~x− ωt)]. Plugging this equation in the wrong-sign wave equation we find
−ω2u− c2k2u = 0 (48)
which (for c > 0) implies that ω = ±ic|k|. But, the time part of the solution u = u0 exp[i(~k ∙~x)] exp[−iωt)] behaves as
exp[−iωt)]. For ω = −ic|k|, the time part behaves as exp[−c|k|t)], i.e., it is exponentially damped in time. But, when
ω = ic|k| the time part behaves as exp[c|k|t)], i.e., it exponentially blows up. Thus, any initial small perturbations
will blow up exponentially in time at a rate that depends on the wavelength of the perturbation, i.e., |k|. The shorter
the wavelength of the perturbation, the larger the wavenumber |k| and the faster the solution blows up. This is the
tell-tale sign of ill-posedness: When in a correct numerical implementation one increases the resolution
and the solution blows up faster, that usually implies an ill-posed initial value problem.
To sum, for the solution of initial value problems we want to have either strongly hyperbolic (which includes strictly
and symmetric hyperbolic PDEs) or parabolic equations, or mixed hyperbolic-parabolic equations.
III. NUMERICAL SOLUTION OF PDES
A. Solving hyperbolic PDEs
We will consider an initial value problem involving the wave equation in one spatial dimension in a box, which is
given by
∂2u
∂t2
− c2 ∂
2u
∂x2
= 0, 0 ≤ x <≤ L, t ≥ 0. (49)
We can always redefine a new time-coordinate through t˜ = ct, and the equation becomes
∂2u
∂t˜2
− ∂
2u
∂x2
= 0, (50)
so we will drop the˜from t and instead we will be dealing with the equation
∂2u
∂t2
=
∂2u
∂x2
, (51)
with boundary conditions
u(t, 0) = u(t, L) = 0, (52)
and initial conditions
u(0, x) = f(x) and
∂u
∂t
∣∣∣∣
t=0
= g(x), (53)
9Similarly we can always refine our spatial coordinate x˜ = x/L, so that the upper boundary L˜ = L/L = 1. So, in what
follows we will be considering L = 1.
We will be solving the wave equation using finite differences. So, first we will create a spatial grid and a time grid,
xi = iΔx, Δx = 1/M, and i = 0, . . . ,M. (54)
this choice warrants that the boundaries are always grid points, i.e., for i = 0, x0 = 0, and for i = M , xM = 1. and
tn = nΔt, n = 1, . . . , N. (55)
where Δt and N will be chosen such that we capture the basic timescale in the problem while we integrate long
enough. Since the wave is traveling at speed 1, and the domain has length 1, then it takes unit time to propagate the
wave from one end to the other. We would like to evolve for multiple such times, e.g., typically up to t = 5, and for
the scheme to be stable Δt/Δx < 1. Typically we will chose M = 100 or so, which will determine Δx, and choose
Δt/Δx = 0.8 which will determine Δt. On this grid we will denote the solution at t = tn and x = xi as uni , i.e.,
uni = u(tn, xi).
The next step is to approximate the derivatives that appear in the wave equation. Starting with the right-hand-side
of Eq. (51), we approximate the second-order derivative using a second-order accurate finite difference, i.e.,
∂2u
∂t2
∣∣∣∣n
i
=
uni+1 + u
n
i−1 − 2uni
Δx2
. (56)
This equation can be viewed as a set of M coupled ordinary differential equations. As in the case of ODEs we can
define the first time derivative as a new variable and evolve a system of two ODEs at every single point of the spatial
grid. The method of solving PDEs in this way is called the method of lines, and is the standard method. Before we
use more sophisticated solvers for the time-integration (e.g. RK4), let us also finite difference the second-order time
derivative using also a second-order accurate sencil. The difference equation Eq. (56) then becomes
un+1i + u
n−1
i − 2uni
Δt2
=
uni+1 + u
n
i−1 − 2uni
Δx2
. (57)
Solving this equation with respect to un+1i we find
un+1i = 2u
n
i − un−1i + λ2(uni+1 + uni−1 − 2uni ), i = 1, . . . ,M − 1, (58)
and boundary conditions
un+10 = 0 = u
n+1
M (59)
where λ = Δt/Δx is called the Courant-Friedrich-Lewy (CFL) number, and must be λ < 1 for numerical stability.
Notice that this is a 3-time level numerical scheme, i.e., to obtain the solution at the n + 1 step we need the solution
at two previous time-levels, un and un−1. This, creates a problem initializing the algorithm described by Eq. (58),
since we will have u0i from the initial data, but we need also u
1
i to start the algorithm. Fortunately, the initial data
must also specify the time derivative of u. In other words at t = 0 we know
∂u
∂t
= g(x). (60)
The simplest way to start the algorithm then is to use a first-order approximation to the time derivative, i.e.,
u1i − u0i
Δt
= g(xi) ⇒ (61)
u1i = u
0
i + g(xi)Δt. (62)
note that if g(x = 0) = g(x = 1) = 0 and u(0, x = 0) = u(1, x = 1) = 0, then u1i will respect the boundary conditions,
i.e., u10 = u
1
M = 0. So, with u
0
i = f(xi) and u
1
i now given from Eq. (62) we can start the algorithm described by
Eq. (58) for n = 1. The only problem is that this initialization will be only first-order accurate.
We can do better than that if f(x) is twice differentiable. The Taylor expansion in t only of u is as follows
u(t1, xi) = u(0, xi) + Δt
∂u
∂t
∣∣∣∣0
xi
+
Δt2
2
∂2u
∂t2
∣∣∣∣0
xi
+ O(Δt3). (63)
10
But, from the initial conditions and the differential equation we have
u(t1, xi) = u(0, xi) + g(xi)Δt +
Δt2
2
∂2f
∂x2
∣∣∣∣
xi
+ O(Δt3). (64)
Thus, the more accurate way to start the numerical integration is to set
u1i = u
0
i + g(xi)Δt +
Δt2
2
∂2f
∂x2
∣∣∣∣
xi
. (65)
Note, that care must be taken such that ∂
2f
∂x2
∣∣∣∣
x0
= 0 = ∂
2f
∂x2
∣∣∣∣
xM
, so that u(t1, x0) = 0 = u(t1, xM ). We will be
comparing the solutions obtained when initialized either with Eq. (62) or Eq. (65).
The algorithm for solving the initial value problem
∂2u
∂t2
=
∂2u
∂x2
, (66)
with boundary conditions
u(t, 0) = u(t, 1) = 0, (67)
and initial conditions
u(0, x) = f(x) and
∂u
∂t
∣∣∣∣
t=0
= g(x), (68)
is as follows
Input: number of timesteps to evolve N , CFL number λ, integer M , initial conditions f(x) g(x).
Output: solution uni for each i = 0, . . .M , and n = 0, . . . N .
Step 1: Set Δx = 1./M , Δt = λΔx, t = Δt
Step 2: Introduce arrays of length M + 1 called x (for the coordinates), unew and uold, uold2 for the new, the previous
and the previous previous timestep solutions.
Step 3: For i = 0, . . .M set x[i] = iΔx, uold2[i] = f(x[i]), and use Eq. (62) or (65) to set uold[i]. Make sure that
uold[0] = uold[M ] = 0, so that the boundary conditions are respected.
Step 4: Main time integration loop;
for (int n=2; n{
// Update the interior spatial points, i.e., everything but the boundary points
for (int i=1; i{
unew[i]=2.*uold[i]-uold2[i]+lambda*lambda*(uold[i+1]+uold[i-1]-2.*uold[i]);
}
// Set the boundary conditions
unew[0]=0.;
unew[M]=0.;
t+=DeltaT; // Update the time
Output t, x[i], unew[i] to a file named based on the timestamp.
A code snippet for this is given below.
// Set the uold2=uold and uold=unew to set up for the next timestep
for (int i=0; i<=M; i++)
{
uold2[i]=uold[i];
uold[i]=unew[i];
}
}
11
Step 5: The procedure is complete.
Recall that to output a file use the following construct in C++
#include
#include
using namespace std;
ofstream myfile ("example.txt");
if (myfile.is_open())
{
myfile << "# Column 1: t Column 2: x, Column 3: u(x)\n";
for (int i=0; i<=M; i++)
{
myfile << t << " " << x[i] << " " << unew[i] << endl;
}
myfile.close();
}
else cout << "Unable to open file";
However, what we really want is to change the name of the file as time changes, and add the timestamp to the
filename. The following snippet of code achieves this
#include
#include
#include
using namespace std;
int main ()
{
string filename;
int out_every=1; ever how many timesteps to output solution files here set to 1
double dt=0.1;
for (int n=0;n<10;n++){
if (i%out_every==0){ //
filename="u_vs_x_"; // Filename prefix
double t=n*dt;
string s=to_string(t); // Convert double t to string
filename+=(s+".dat"); // Concatenate filename prefix with t value as string and extension .dat
ofstream myfile (filename); // open the output file stream with name that of the filename
if (myfile.is_open())
{
myfile << "# Column 1: t Column 2: x, Column 3: u(x), Column 4: uexact(x)\n";
for (int i=0; i<=M; i++)
{
// output time x coordinate, numerical solution, and exact solution
myfile << t << " " << x[i] << " " << unew[i] " " << uexact(t,x[i]) << endl;
// Note that uexact is a function of the exact solution
}
myfile.close();
}
else cout << "Unable to open file";
}
}
return 0;
}
12
This code snippet will create 10 files with names like
u_vs_x_0.100000.dat, u_vs_x_0.200000.dat,
where the number corresponds to the timestamp, and each of these files will have M + 1 lines with three columns
having values for time, x, and the solution u, respectively. Note that for the above code to work you must compile
with C++11 standard, i.e., the compilation command must be supplemented with the “-std=c++11” flag, i.e., “g++
-std=c++11”. Also, note that the above header files, include statements are those necessary for this code to work.
You may need additional include statements for the rest of your code. A beter coding approach is to include
the above lines into a void function as follows
void fileoutput(string filename,double t,double *x,double *unew,int M)
{
// filename only enters with a prefix
string s=to_string(t); // Convert double t to string
filename+=(s+".dat"); // Concatenate filename prefix with t value as string and extension .dat
ofstream myfile (filename); // open the output file stream with name that of the filename
if (myfile.is_open())
{
myfile << "# Column 1: t Column 2: x, Column 3: u(x), Column 4: uexact(x)\n";
for (int i=0; i<=M; i++)
{
myfile << setprecision(16) << setfill(’ ’) << setw(20) << t << " " << x[i] << " "
<< unew[i] << " " << uexact(t,x[i]) << endl; // output time, x coordinate and solution
}
myfile.close();
}
else cout << "Unable to open file";
}
After you have the file output function, you input the string filename using the filename prefix as in the previous
lines, and then you can call the file output function anytime you want to output a file.
Exercise 2: Implement numerically the algorithm for the wave equation we just described. Use Δx = 0.01, λ = 0.5,
N = 200. Use as initial conditions.
u(t, 0) = f(x) = sin(πx), 0 ≤ x ≤ 1. (69)
∂u
∂t
∣∣∣∣
t=0
= g(x) = 0, 0 ≤ x ≤ 1. (70)
Implement the initialization in Step 3 above, using once Eq. (62) and once Eq. (65). After you code is complete
compare the solution at the final time T = 1 with the exact solution which is given by = sin(πx) cos(πt). Make a plot
of unumerical vs x and the exact solution u(1, x) vs x. Then plot the difference between the numerical and analytic
solution vs x. Run your code again with dx = 0.005, N = 400, λ = 0.5, and plot the difference between the numerical
and analytic solution vs x at T = 1. By what factor do you have to multiply the difference found for dx = 0.005 in
order for it to overlap the difference found for dx = 0.01? Do this exercise using once Eq. (62) and once Eq. (65). Is
the factor different for the two methods?
B. Von Neumann Stability analysis and Lax’s equivalence theorem
We have so far seen that round-off error instabilities can arise that destroy the stability of a numerical integration
scheme. Numerical schemes for PDEs can also be unstable, and they have to be analyzed for each PDE and PDE
scheme separately. The method we will consider here is called the Von Neumann stability analysis.
Let us consider for example the simple IVP of the wave equation, solved with the finite difference scheme we
discussed in the previous subsection, i.e.,
un+1i = 2u
n
i − un−1i + λ2(uni+1 + uni−1 − 2uni ), i = 1, . . . ,M − 1, (71)
13
Since the equation is linear, the same equation is satisfied by u and any perturbations δu in u. If the equation
was non-linear we would have to linearize it about a background solution u to perform the Von Neumann stability
analsysis. Next consider consider harmonic perturbations of the form
uni = exp[ωtn − ikxi)] (72)
Note that from this equation the amplification factor is
Λ =
un+1i
uni
=
exp[(ωtn+1 − ikxi)]
exp[(ωtn − ikxi)] =
exp[(ω(tn + Δt)− ikxi)]
exp[(ωtn − ikxi)] = exp(ωΔt) (73)
Thus, to find whether a numerical scheme is stable or not for a particular PDE (or set of PDEs) we have to use the
PDEs to solve for exp(ωΔt). If we plug Eq. (72) in Eq. (71) we obtain
exp[(ω(tn + Δt)− ikxi)] =
2 exp[(ωtn − ikxi)]− exp[(ω(tn −Δt)− ikxi)] + λ2(exp[(ωtn − ik(xi + Δx))] + exp[(ωtn − ik(xi −Δx))]− 2 exp[(ωtn − ikxi)])
(74)
Diving out the last equation by exp[(ωtn − ikxi)], yields
exp(ωΔt) = 2− exp(−ωΔt) + λ2( exp(−ikΔx) + exp(ikΔx)− 2) (75)
or after using Eq. (73)
Λ = 2− Λ−1 + λ2( exp(−ikΔx) + exp(ikΔx)− 2), (76)
which after using the Euler formula exp(ikΔx) = cos(kΔx) + i sin(kΔx) becomes
Λ = 2− Λ−1 + 2λ2( cos(kΔx)− 1) = 2− Λ−1 − 4λ2 sin2(kΔx/2) ⇒ (77)
where we used the identity cos(2x) = 1− 2 sin2(x) in the last step. The equation then becomes
Λ2 − 2[1− 2λ2 sin2(kΔx/2)]Λ + 1 = 0, (78)
This is a quadratic equation for Λ, whose discriminant is given by
Δ = 4
{
[1− 2λ2 sin2(kΔx/2)]2 − 1} (79)
Thus, the solutions are
Λ± = Σ±

Σ2 − 1 (80)
where Σ = 1−2λ2 sin2(kΔx/2). If Σ > 1, then the + always satisfies Λ+ > 1 and hence the scheme will be numerically
unstable. Thus, for numerical stability we must have |Σ| ≤ 1 which implies
|1− 2λ2 sin2(kΔx/2)| ≤ 1 ⇒ (81)
−1 ≤ 1− 2λ2 sin2(kΔx/2) ≤ 1. (82)
The right inequality is satisfied always, but the left part is satisfied when λ ≤ 1. But, does |Σ| ≤ 1 imply |Λ| ≤ 1? If
|Σ ≤ 1| we have
Λ± = Σ± i

1− Σ2, (83)
Therefore,
|Λ±| = Σ2 + |1− Σ2| = 1. (84)
Therefore, the scheme will always be stable if λ ≤ 1, which means the CFL number must satisfy
Δt
Δx
≤ 1. (85)
14
Having the notion of stability, we also discuss the notion of consistency and convergence.
Definition 1: A consistent discrete numerical scheme for a PDE (system) is one when Δt → 0 and Δx → 0 the
discrete system becomes equivalent to the underlying exact PDE (system).
Definition 2: A convergent numerical scheme for a PDE (system) is one for which
lim
Δx→0,Δt→0
u(tn, xi) = u0(tn, xi), (86)
where u0 is the true solution at t = tn and x = xi of the PDE for given initial data and boundary conditions.
The Lax equivalence Theorem: Consider a well-posed initial value problem for a PDE (system) with constant
coefficients. Then any two of the following 3 conditions imply the other:
• The numerical scheme is consistent.
• The numerical scheme is convergent.
• The numerical scheme is stable.
The most important application of the theorem is on proving convergence especially for applications where we do
not have exact solutions. Typically demonstrating consistency is straightforward, and stability is typically easy to
show by running the code and ensuring the scheme is stable. Thus, by the Lax equivalnce theorem then one proves
convergence. However, convergence and self-convergence studies are always necessary, especially in combination with
Richardson extrapolation to evaluate errors.
C. Parabolic and elliptic PDEs
Before we discuss numerical schemes for solving parabolic and elliptic PDEs let us first try to understand a few
properties of parabolic PDEs. As we mentioned earlier in these notes, the archetypical example of a parabolic PDE
is the heat equation, which in Cartesian coordinates is given by
∂u
∂t
= σ(
∂2u
∂x2
+
∂2u
∂y2
+
∂2u
∂z2
), (87)
with σ > 0 the diffussivity parameter. Let us know consider that we have some solution u, and with given boundary
conditions we apply harmonic perturbations spatial to it of the form u = u + δu, δu = δu˜ exp(i
∑3
i=1 k
ixi), with
x1 = x, x2 = y, x3 = z, and k1 = kx, k2 = ky, k3 = kz. Then since
∂2δu
∂xi
2 = −(ki)2δu (88)
the heat equation for these perturbations becomes
∂δu˜
∂t
= −σk2δu˜, (89)
where k2 = (kx)2 + (ky)2 + (kz)2 is the magnitude of the wavevector. For any value of k this last equation is a simple
ordinary differential equation with exact solution given by
δu˜ = δu˜0 exp(−σk2t). (90)
In other words, the shorter the wavelength of perturbation, i.e., the higher the wavenumber, the faster that pertur-
bation mode decays to 0. Thus, short-wavelength modes decay extremely fast, this is why parabolic equations have
smoothing properties. If you start with extremely noisy initial data and evolve them with a parabolic equation, these
data will become smooth very very rapidly. That’s what a diffusion (parabolic) equation does. Notice that associated
with Eq. (90) there exists a characteristic damping timescale
τk =
1
σk2
=
λ2
4π2σ
. (91)
This is important to understand qualitatively the stability conditions of numerical schemes for solving parabolic
equations.
15
1. Solving parabolic PDEs
Let us now focus on one spatial dimension, so that the heat equation becomes
∂u
∂t
= σ
∂2u
∂x2
. (92)
with boundary conditions
u(t, 0) = u(t, L) = 0, (93)
and initial conditions
u(0, x) = f(x). (94)
We can always redefine our spatial coordinate to be dimensionless x˜ = x/L, so that the upper boundary L˜ = L/L = 1,
and then our evolution equation becomes
∂u
∂t
=
σ
L2
∂2u
∂x˜2
. (95)
In addition, we can define a new dimensionless time through t˜ = t/(L2/σ), so that we finally have to solve
∂u
∂t˜
=
∂2u
∂x˜2
. (96)
Again, we can drop the˜and consider the following IVP
∂u
∂t
=
∂2u
∂x2
. (97)
with boundary conditions
u(t, 0) = u(t, 1) = 0, (98)
and initial conditions
u(0, x) = f(x). (99)
Next, as in the case of the wave equation we will discretize space and time as
xi = iΔx, Δx = 1/M, and i = 0, . . . ,M. (100)
this choice warrants that the boundaries are always grid points, i.e., for i = 0, x0 = 0, and for i = M , xM = 1. and
tn = nΔt, n = 1, . . . , N. (101)
where Δt and N will be chosen such that we capture the basic timescale in the problem while we integrate long
enough. Since the characteristic timescale in the problem is given by Eq. (91), the longest timescale corresponds to
the longest lengthscale in the problem, and that is the length of the box 1. Thus, we would like to evolve for a few
timescales
τL =
1
4π2
. (102)
For the scheme to be stable we should resolve the smallest timescale in the problem, which corresponds to the shortest
wavelength we can resolve on the grid, which has size λshortest=2Δx (this is called the Nyquist frequency for a grid).
Thus, for stability
Δt . 4Δx
2
4π2
=
Δx2
π2
. (103)
Notice, that the higher the resolution (smaller Δx), the smaller the timestep must become for numerical stability.
This is the case for hyperbolic equations, too, but in this case the timestep must decrease quadratically with Δx,
16
while in the wave equation equation case it was linearly. This imposes a very stringent constraint on the timestep for
parabolic equations. However, there are ways around this by choosing implicit integration schemes.
The next step is to approximate the derivatives that appear in the heat equation. Starting with the right-hand-side,
we approximate the second-order derivative using a second-order accurate finite difference, i.e.,
∂u
∂t
∣∣∣∣n
i
=
uni+1 + u
n
i−1 − 2uni
Δx2
. (104)
This equation can be viewed as a set of M coupled ordinary differential equations. As in the case of ODEs this could
be integrated with, e.g. RK4. But, let us also finite difference the time derivative using a forward difference method.
The difference equation Eq. (104) then becomes
un+1i + u
n
i
Δt
=
uni+1 + u
n
i−1 − 2uni
Δx2
. (105)
Solving this equation with respect to un+1i we find
un+1i = u
n
i + λ(u
n
i+1 + u
n
i−1 − 2uni ), i = 1, . . . ,M − 1, (106)
and boundary conditions
un+10 = 0 = u
n+1
M (107)
where λ = Δt/Δx2 is again called the CFL number, and strictly speaking must be λ < 1/2 for numerical stability.
Notice that this is a 2-time level numerical scheme, i.e., to obtain the solution at the n + 1 step we need the solution
at un. However, the time derivative is not as accurate as in the wave equation where we adopted a second-order
accurate scheme, whereas here we adopted a first-order accurate one.
Let us next derive the stability condition for the scheme of Eq. (106). We will again consider harmonic perturbations
uni = exp[ωtn + ikxi] (108)
from which the amplification factor becomes
Λ =
un+1i
uni
= exp(ωΔt). (109)
Plugging the harmonic perturbations in Eq. (106) yields
exp[ωtn + ikxi] exp[ωΔt] = exp[ωtn + ikxi] + λ exp[ωtn](exp[ik(xi + Δx)] + exp[ik(xi −Δx)]− 2 exp[ikxi]). (110)
Diving out by exp[ωtn + ikxi] then we obtain
Λ = exp[ωΔt] = 1 + λ(exp[ikΔx)] + exp[−kΔx]− 2) = 1 + 2λ(cos(kΔx)− 1) = 1− 4λ sin2(kΔx/2). (111)
For stability we must have |Λ| ≤ 1, so
−1 ≤ 1− 4λ sin2(kΔx/2) ≤ 1. (112)
The right-hand inequality is always satisfied, but the left one requires
λ sin2(kΔx/2) <
1
2
(113)
and since sin2(kΔx/2) ≤ 1, the inequality is satisfied for any if
λ =
Δt
Δx2
≤ 1
2
. (114)
This is close to our intuitive stability condition of Eq. (103), which states
Δt
Δx2
. 1
π2
, (115)
17
and is more stringent since it requires λ ≤ 1/π2 ∼ 0.1, while Eq. (114) requires λ ≤ 0.5. But, again physi-
cal/mathematical intuition can always do the job for us, even if we do not perform the careful stability analysis.
Starting with our intuitive stability condition, we could use trial and error to find how high λ can be before the code
becomes numerically unstable.
To sum, Eq. (106) subject to the boundary conditions (107) provide an algorithm for solving the heat equation.
Exercise 3: Implement numerically the scheme algorithm for the heat equation Eq. (106). Use Δx = 0.1, λ = 0.45,
N = 200, and Δx = 0.05, λ = 0.45, N = 800. Use as initial conditions.
u(t, 0) = f(x) = sin(πx), 0 ≤ x ≤ 1. (116)
and boundary conditions u(0, t) = u(1, t) = 0. Compare your numerical solution to the analytic solution
u(t, x) = exp(−π2t) sin(πx). (117)
2. Elliptic PDEs
Elliptic PDE cannot be solved as initial value problems. Typically these equation are also finite differenced, and
then a set of coupled algebraic PDEs has to be solved, the number of which equals the number of points on the spatial
grid. However, there exists another method for solving elliptic PDEs that is called the relaxation method. The idea is
to convert the elliptic PDE into a parabolic PDE in some fiducial time parameter. So, considering Poisson’s equation
∇2Φ− 4πGρ = 0 (118)
we can introduce a fiducial time t and convert this equation to
∂Φ
∂t
= ∇2Φ− 4πGρ (119)
set some guess initial conditions, and the correct boundary conditions that correspond to the elliptic equation and
then solve this as an initial value problem for parabolic equations. The damping/smoothing properties of parabolic
equations that we discussed guarantee that as time evolves the time variations of Φ will go to 0, i.e., lim t→∞ ∂Φ∂t = 0.
Thus, when evolving for long times Eq. (119) will relax to ∂Φ∂t = 0, and hence ∇2Φ − 4πGρ = 0, i.e. the solution
will satisfy the original elliptic equation. This is the essense of the numerical methods called relaxation methods for
solving elliptic PDEs.

欢迎咨询51作业君
51作业君

Email:51zuoyejun

@gmail.com

添加客服微信: Fudaojun0228