0% found this document useful (0 votes)
110 views158 pages

INTRODUCTION TO PYTHON Version 1 WITH SO

Uploaded by

codeus007
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
110 views158 pages

INTRODUCTION TO PYTHON Version 1 WITH SO

Uploaded by

codeus007
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 158

School of Mathematics and Physics – UNIVERSITY OF PORTSMOUTH

BASIC INTRODUCTION TO PYTHON PROGRAMMING

DR SERGI SIMON
Lecture Notes used in Semester 1 of the module INTRODUCTION TO
COMPUTATIONAL PHYSICS (U24200, years 2019/2020, 2020/2021)

Solutions to all exercises can be accessed in


https://github.com/Sergi-Simon/INTRODUCTION-TO-PYTHON-PROGRAMMING
FURTHER READING:
Online courses (different platforms)
Hetland, M. L., Beginning Python From Novice to Professional
Norton, P. et al., Beginning Python, Wiley Publishing Inc.
MathWorks Inc, The Student Edition of Matlab
Wilson, H. B. et al, Advanced Mathematics And Mechanics

Portsmouth, July 15, 2021


I Introduction to Python
• Created by Guido Van Rossum (1991). OS Amoeba and user interface Bourne shell did not mesh
well. He devised a programming language to communicate both more fluidly.
• Despite the logo, name stems from Van Rossum’s predilection for Monty Python.
• Programming languages can be high-level or low-level:
– Low-level languages: syntax is closer to machine language (0’s and 1’s).
– High-level languages: their syntax is closer to a natural human language.
Python is a high-level language – so much so, that an English speaker can understand what any
Python routine is doing.
• Clear and simple grammar: no symbols, e.g. semicolon, used by other languages.
• Strongly typed (like Java, C++, unlike PHP) e.g. integer variables distinguishable from strings
• It is also dynamically typed, i.e. you can establish the type at any point – whereas in a static type
system, e.g. Java or C++, you have to declare the variable type before using it.

1
• Python is an object-oriented (OOP) programming language. We will see what this means later.
• Open source.
• Easy to learn. Hence the usual choice to introduce neophytes to programming languages.
• Large standard library, i.e. multiple useful classes and libraries come by default.
• It is an interpreted language, i.e. does not need the intermediate step of compilation.
• Versatile: useful to create any type of application (same as Java).
• Multi-platform: can be executed in Linux, Windows, Mac, ...
• Basic Installation and/or use on your own computer:
– Go to https://www.python.org/downloads and download latest version...
– ... or download Anaconda Distribution from https://www.anaconda.com/distribution
– ... or use http://sciserver.org
• Basic use on University computers:
– AppsAnywhere + Anaconda distribution

2
How do we write and execute Python:
• With files having extension .py. For instance if we open a file called hello.py using an editor
(the default IDLE Python editor, Spyder, Text Sublime, Notepad++, Eclipse, ...) and write in it:
print ("Hello, world")
save it and run it on a command line writing python hello.py (using the IDLE shell or a
Mac/Windows/Linux terminal) and our output will be
Hello, world

• You can use any Python-adapted environment via Anaconda, e.g. Spyder itself to run the .py file.
• You can write command lines (using the IDLE shell or a Mac/Windows/Linux terminal), e.g.
C:\Username>python or C:\Username>python3

which will allow you to test short amounts of code if you don’t want to create a new .py file.
• You can use Sciserver.org or Anaconda to run Jupyter notebooks which combine text with
code.
• You can use any other coding platform, e.g. REPL.it.
• See the next four pages for examples on how to edit and compile Python files.

3
• Create the file (for instance hello.py), using Spyder, any basic text editor, e.g. Notepad or
Sublime Text, or the default Python IDLE shell (if you first installed Python on your computer,
see https://www.python.org/downloads):

4
• Save your file. You can open it again with any editor as well as open a file created by someone else.
• You can then run the program; there are different options depending on your editor:

1. this list is not exhaustive; there are many editors


that you can work with
2. most text editors, however, do not have a Python
compiler
3. you can also search for online Python editors and
compilers on the internet

5
• You can use SciServer to create files and run them from a Jupyter notebook, but it is less practical:

1. create or open text file and edit it


2. rename it as having .py extension and save in Sciserver folder
3. open or create a Jupyter file
4. load or run your Python program from the Jupyter file

Although if your .py file had been created with another editor (e.g. those in page 1), then
uploading it to SciServer and running it on a Jupyter notebook is still a comfortable option.
6
• You can also use a command prompt to run Python programs. I will illustrate it on Mac (and it
will be the same as in Linux); be sure to google images of Windows command prompts running
Python if you are a Windows user.

1. create/open/edit/save .py file with any editor


(page 1)
2. open a terminal or command prompt and make
sure your working directory is the one containing
the file
3. write python or python3 followed by file name
4. if a written output is expected, it will appear on
the same terminal; other outputs (modifying files,
etc) will be visible elsewhere, more on this in a few
weeks

7
Remarks
• We will be using mostly Spyder in class because it has the advantage of combining Python source
code and output in the same console, and because it is available for you to use in University
computers, but this is only one out of many possible methods.
• There are plenty of ways to edit, run, and edit and run Python files, e.g. Visual Studio, and we
strongly encourage you to search them on the internet.
• The one thing you must always remember, is that Python files have extension .py.
• Python files can import or ”call” other Python files, and we will be studying this in a few weeks.

8
1 Types, operators, variables and syntax basics
Types
❥❥ ❙❙❙
❥❥❥❥❥❥❥ ❙❙❙
❙❙❙
❥❥ ❙❙❙
❥❥❥❥❥❥❥ ❙❙❙
❙❙❙
❥❥❥ ❙❙❙
❥❥❥❥ ❙
Strings
Numerical Boolean
t ◗◗◗ (' ', " ", ''' ''') ❋❋
tt ◗◗◗ ❋❋
ttt ◗◗◗
◗◗◗
❋❋
❋❋
t
tt ◗◗◗ ❋❋
ttt ◗◗◗ ❋❋
t ◗◗◗ ❋❋
tt ◗ ❋❋
tt
Floating-point Complex
❋❋

Integers Numbers Numbers True False
(int)
(float) (complex)

9
Operators
✆ ✿✿
✆✆ ✿✿
✆ ✿✿
✆✆ ✿✿
✆✆✆ ✿✿
✆ ✿✿
✆✆✆ ✿✿
✆ ✿✿
✆✆✆ ✿✿
✆✆ ✿✿
✆✆ ✿✿
✆✆ ✿

Arithmetic Comparison Assignment Others


==
Logical
Add + Equal Definition = is
Subtract - Not equal != Increment +=
Greater than > and -=
is not
Multiply * Decrement in
Divide / Less than < *=
Greater than or /=
not in
Modulus % >= AND
Power ** or equal to %=
Floor Smaller than not **= OR
//
or equal to
<= //= XOR
division

10
• Variables correspond to spaces in the memory of a computer (containers) wherein a data value
(numerical, string, etc) is stored, and can be changed during execution (hence the name).
• Unlike other languages, Python has no command for declaring a variable. A variable is created
the moment you first assign a value to it.
• Variable names are case-sensitive (i.e. name and Name are different) and:
– cannot start with a number, e.g. 2n is not a valid variable name.
– must start with a letter or with the underscore character
– can only contain alpha-numerics and underscores (A-z, 0-9, and ), e.g. my number
– optional: i, t usually reserved for loop indices,
– optional: n, m traditionally reserved for integer variables.
• The type of a variable is not specified by the container but by the content. For instance, in other
languages such as Java or C++, the type is defined by the container, i.e. the variable itself:
int number = 5;

whereas if in Python we write number = 5, this is automatically a numerical and integer variable
because it is given by the content (i.e. the value), not by the container.
• Python is 100% object oriented, thus everything (including variables) are objects.

11
• If we open a file (we can call it variable.py) and write the following:
name=1
print ( type( name ) )
save the program and run it, we will obtain the output <class 'int'>.
• If we repeat the process replacing name=1 by name=2.4, we obtain <class 'float'>
• Replacing it by name="hello" or name='hello', we obtain <class 'str'>. Strings are
surrounded either by single or by double quotation marks, i.e. ’hello’ is the same as ”hello”
• We can also use triple quotes to allow multiple lines for a single string, e.g.
name= '''This is name= """This is
a sample message''' a sample message"""
or
print ( type( name ) ) print ( type( name ) )
print ( name ) print ( name )

we obtain
<class 'str'>
This is
a sample message

12
• Comments are used to explain Python code. Commented lines are not executed.
• Everything in the same line and after # will be commented and thus ignored by Python:
# The print function yields
# the quoted output onscreen produces output hello world.
print ( "hello world" ) # this is it

• Triple quotes serve the same purpose but can comment multiple lines at once:
'''The print function yields """The print function yields
the quoted output onscreen''' or the quoted output onscreen"""
print ( "hello world" ) print ( "hello world" )

produce the same output hello world as above.


• \n and \t: newline and tabulation; print("Hello \t Goodbye \n hello again") produces
Hello Goodbye
hello again
You will often combine strings and numbers, e.g.
number1 , number2 = 1,2
print("The sum of ",number1," and ",number2," is ",number1+number2)
You can create more examples yourselves in this week’s Jupyter notebook.
13
2 Functions
• A function is a set of lines (i.e. a block of code), working as a single unit and carrying out a specific
task only when it is called. A function:
– can return data
– can have data (known as parameters or arguments) passed into it
• Functions can be:
– built-in or predefined, e.g. print and type which we have already seen, and many others
(abs, max, ...), see e.g. https://docs.python.org/2/library/functions.html.
– created by the user (meaning: you), which we will learn how to do these next weeks.
• Advantage: the ability to reuse code (whenever it is necessary)
• Syntax: depending on whether or not arguments are passed into the function,
def function name (): def function name (parameters):
instructions... or instructions... every single line after
return... (optional) return... (optional) def must be indented

• To call the function, we use the function name followed by brackets: function name () in the
first case, function name (parameters) in the second one.
• Functions are called methods if they are defined in a class (which we will explain in the future).
14
• First example: write a program named conversion.py as follows:
def miles to km ( miles ):
return miles*1.6
print(miles to km(7.1))
Run the file: you should obtain 11.36 as an output. Change 7.1 and you will change the output.
• Alternatively, you could have used a variable to store the product before returning it:
def miles to km ( miles ):
x=miles*1.6
return x
print(miles to km(7.1))
• Variable x above was local after the assignment, i.e. recognised only by the function it was first
defined in. This is in contrast to other languages where variables are global (their range encom-
passes the entire program) unless specified otherwise. For instance, in the following piece of code
y=1.6
def miles to km ( miles ):
x=miles*y
return x
print(miles to km(7.1))
y is global with respect to miles to km (or local to a wider function whose boundaries are not
visible here), whereas x is local to miles to km because it was first defined therein.
15
• Let us see this with an incorrect example. Assume our entire program reads as follows:
def miles to km ( miles ):
x=miles*1.6
return x
print (x)
The output will be an error message containing the following:
NameError: name 'x' is not defined

This is because x was first defined (not just referenced) within function miles to km, hence local
to it. The rest of this program, therefore, does not acknowledge x.
• However, if we had first defined x outside of the function then called the function,
x=1
def miles to km ( miles ):
x=miles*1.6
return x
print (miles to km ( 3 ))
print (x)
we would have obtained 1 as the output for x (and 4.8 for the other print command). The
program is technically correct, but x is reused in a misleading way.

16
• Even if we made no use of argument miles and operated only with apparently global values,
x=1
def miles to km ( ):
x=3*1.6
return x
print (miles to km ( ))
print (x)
The outputs will still be 4.8 and 1 respectively. x would remain unchanged and it would also be
one variable too many for the program to be optimal.
• We said before that defining a variable in a function immediately rendered it local. If no value is
assigned to it in that function, however, it is global. Hence if a variable is only referenced inside
a function, it is global. However if we assign any value to a variable inside a function, its scope
becomes local to that unless explicitly declared global. This will also yield an error message:
x=1
def miles to km ( ):
x=x*1.6
return x
print (miles to km ( ))
print (x)
because x had been defined outside the scope of the function, thus x=x*1.6 requests overriding a
variable (the right-hand side x) that had not been defined inside the function.
17
• Way to solve the problem, although the function will still not be optimal:
x=1
def miles to km ( ):
y=x*1.6
return y
print (x)
print(miles to km ( ))
x remains global because we did not re-assign values to it inside the function. The function uses x
correctly and returns adequate value, but still depends on defining x before calling the function.
• This, however, is better:
x=1
def miles to km ( miles ):
y=miles*1.6
return y
print (x)
print(miles to km ( x ))
x has not changed thus is the value of the original variable. The value of the function of x, namely
1.6, is printed correctly in the last line. There is clear distinction between the original variable x
and the final value miles to km ( x ).
• Besides, in the above program we can call the function for any argument, e.g. miles to km ( 4 )
will yield 6.4.
18
• Hence these are correct definitions (and call examples) for this function
def miles to km ( miles ):
y=miles*1.6
return y
print(miles to km ( 4.7 )) #or x=4.7 followed by print(miles to km(x))

def miles to km ( miles ):


return miles*1.6
print(miles to km ( 4.7 )) #or x=4.7 followed by print(miles to km(x))

• The local variable y in the first example could have been replaced by any other variable, e.g.
def miles to km ( miles ):
whatever=miles*1.6
return whatever
print(miles to km ( 4.7 )) #or x=4.7 followed by print(miles to km(x))

x would do as well, even if you defined x=4.7 outside of the function – but we do not encourage
the use of the same variable name for both local and global variables.
• You can practice by modifying this week’s Jupyter notebook, see what you get by playing with the
variables.
19
• What if we wanted to keep x global inside the function? This is not necessary or advisable in
this example, but it could be elsewhere. We use the keyword global in order to tell Python that,
contrary to its usual practice, this variable that is being changed within the function is not local:
# This function modifies global variable x
def miles to km ( ):
global x
x=x*1.6
return x
x=1
miles to km()
print(x)
miles to km()
print(x)
Calling the function twice changes the value of global variable x twice. Our printed output will
be 1.6 followed by 2.56.

20
• Passing arguments into the function, however, will often be the advisable path. For instance:
def add ( ):
n1=3
n2=2
return n1+n2

print(add())
print(add())
print(add())
will return 5 thrice because the variables involved in the sum could not be modified. However,
def add ( n1, n2 ):
return n1+n2

print(add(3, 2))
print(add(3,2.2))
print(add(0,-1))
and generally speaking any call of this add will work correctly if we pass any two arguments to it.

21
• You can pass any number of arguments. Assume we want to check the distributive property of
integers, a · (b1 + b2) = a · b1 + a · b2:
def dist lhs ( a, b1, b2 ): Forms enclosed in brackets, i.e.
return a*(b1+b2) parentheses are treated as atoms
def dist rhs ( a, b1, b2 ): and their content is computed
return (a*b1)+(a*b2) before releasing it as part of a
print(dist lhs(1,2,4) == dist rhs(1,2,4)) larger operation

will return True (and will remain so if you change 1,2,4 by any other three integers on both sides).
• Proper programming habits entail using ancillary local variables to store intermediate steps in
functions and general calculations. For instance this:
def dist lhs ( a, b1, b2 ):
var1=b1+b2
var2=a*var1
return var2 In this case the number of oper-
def dist rhs ( a, b1, b2 ): ations was the same, but in other
var1=a*b1 functions this will not be the
var2=a*b2 case
return var1+var2
print(dist lhs(1,2,4) == dist rhs(1,2,4))

yields the same output as the above, but abides by a way of writing that will be useful to minimise
the number of calculations in future programs.
22
• A function that calls itself is recursive. For instance, let us compute the factorial of n:

n! := n · (n − 1) · · · 2 · 1 n being a non-negative integer

Notice that n! = n(n − 1)!. Using this, consider the following recursive function:
def factorial ( n ):
return n*factorial(n-1)
If this function works, then calling factorial (4) will trigger the following:

factorial (4) calls


❝❝❝❝❝❝❝❝❝
11 factorial (3) calls
❝❝❝❝❝❝❝❝❝
11 factorial (2) calls❞❞❞❞❞❞❞❞❞❞❞❞❞22 . . .
❝❝❝❝❝❝❝❝❝ ❝❝❝❝❝❝❝❝❝ ❞❞❞❞
4*factorial (3) 3*factorial (2) 2*factorial (1) ...
An error message ensues because the function needs a base (or terminating) case, i.e. a condition
breaking the recursion chain. Otherwise it keeps calling itself: factorial(0), factorial(-1), ...
• This is done as follows (remember we will study conditionals more in detail later on):
def factorial ( n ):
if n==0:
var=1
else: based on the convention that 0! = 1! = 1:
var=n*factorial(n-1)
return var

then calling factorial (4) will produce 24, calling factorial (5) will produce 120, etc.
23
• A function performs no further action after a return line, hence equally correct ways are:
def factorial ( n ):
def factorial ( n ):
if n==0: thus else is not
if n==0:
return 1
return 1 necessary – think why!
else:
return n*factorial(n-1)
return n*factorial(n-1)

• Example of functions calling other functions: once(you) have defined factorial in any of the
n
above ways, you can compute the binomial coefficient m , i.e. the number of ways to choose m
elements from a set of n elements. This is given by the following:
(n) n!
= (other notations: C (n, m), n Cm , n C m , ...…)
m m!(n − m)!
Our program binomial.py would read, for example,
def factorial ( n ):
if n==0:
return 1
return n*factorial(n-1)

def binomial ( n, k ):
Conversion to int type is
var1=factorial(n) necessary because input
var2=factorial(k) processes data as strings
var3=factorial(n-k)
return int(var1/(var2*var3)) by default.

n=int(input("Write number n: "))


k=int(input("Write number k: "))
print( "The binomial coefficient is ", binomial(n,k))

24
Exercises
• We like to avoid error messages, but we still have not explained exceptions. For the time being,
adapt the above function binomial to( return customised messages if the numbers n,k we feed the
function make the computation of n
)
k impossible as a positive integer.
• Write a program in a file exponential.py which takes variables x (a float) and k (an integer) as
inputs, and returns the following approximation of the exponential function ex:
k
x2 x3 xk ∑ x i
f (x, k) = 1 + x + + + ··· + = (remember x0 = 0! = 1)
2 6 k! i=0
i!

Compare the output of this function for any x and for large values of k, with the numerical value
ex or exp at x produced by any calculator. We have not seen loops yet so think of a way to write
this function without them.
• Using the previous item, write a program called hyperbolic.py which approximates, for any given
float variable x, the hyperbolic sine and cosine of x:
ex + e−x ex − e−x
cosh x = , sinh x =
2 2

25
• The Fibonacci numbers are defined as follows:

F0 = 0 , F1 = 1 , Fn = Fn−1 + Fn−2, n > 1.


Thus F0, F1 are the initial conditions or values for sequence 0, 1, 1, 2, 3, 5, 8, 13, . . .
– Write a program fibonacci1.py taking any input n and returning the Fibonacci number Fn
according to the above construction.
– Another way of obtaining Fn is as follows:
{ √
φn − ψn φ= 1+ 5
≃ 1.618033 . . . is the golden ratio
Fn = √ , where 2
5 ψ= −φ−1 = − φ1 ≃ −0.618033 . . .

Write another program fibonacci2.py to compute Fn in this manner for any n obtained by
input. You will have to import the math module in order to compute square roots. The way
to do so is to write import math at the very beginning of the program, and then any time you

need to compute a square root y all you have to do is write math.sqrt( y ).
• Write a program temperature.py that converts degrees Fahrenheit to degrees Celsius and vice
versa. The program should give the user all the options you deem reasonable and should take
these options (and the numbers) as input.

26
3. Collection data types
• Collection types or arrays can be divided in four categories in Python:
– Lists, written with square brackets [...]
– Tuples, written with round brackets (...)
– Sets, written with curly brackets {...}
– Dictionaries, with curly brackets and double-barreled entries {⋆:⋆,...,⋆:⋆}
• Their differences are based on different criteria:
– Whether they are ordered or unordered:
∗ in an ordered collection, order is relevant, for instance [a,b,c]!=[b,a,c]
∗ in an unordered collection, order is irrelevant, for instance {a,b,c}=={b,a,c}
– Whether they are indexed or unindexed:
∗ in an indexed array, an item can be accessed by referring to its index number, e.g. x[0] will
be the first element in x, x[1] the second, etc.
∗ in an unindexed array, this is impossible
– Whether they are changeable or unchangeable after their first definition.
– Whether or not they allow duplicate members.

27
Lists
• Lists [ ... ] are data structures allowing us to store large amounts of values. They:
– are ordered, i.e. if you change the order you change the list;
– are indexed, i.e. its elements can be located by their position;
– are dynamically changeable (both in length and in content) during the program;
– allow member duplication.
– allow membership checks with in.
• In Python (unlike in other languages) lists allow the storage of different types of values, e.g.
x=[1,"hello",2.0]
even lists can be members of other lists:
y=[1,"goodbye",x]
• To check whether an element is a member of a list, we use in:
mylist=[1,2,3,-1,1,0,0,"u"]
True
print(3 in mylist)
−−−−−−−−→ True
print(1 in mylist) output False
print("a" in mylist)

28
• The index of an element is the position it occupies in the list minus one (indices start in 0), e.g.
y = 1 "goodbye" x

You can check this yourselves writing

↑ ↑ ↑
print (y[0]), print(y[1]), or print(y[2]).
y[0] y[1] y[2]

• List members can be changed by reference to index:
x=[1,2,3,"name",3.2,2]
x[2]="something else" −−−−−−−−−−→ [1,2,"something else","name",3.2,2]
print ( x ) output

• Negative indexing starts from the opposite end. For instance, print(mylist[-4]) yields 1.
mylist = 1 2 3 -1 1 0 0 "u"
mylist[0] mylist[1] mylist[6] mylist[7]
|| || || ||
mylist[-8] mylist[-7] mylist[-2] mylist[-1]

• The length of a list is denoted by len. Thus the indices of a list L are within the range 0, ..., len(L)-1
.
x=[1,-4.1,"a","c",3,56.3,-10.1]
print ( len(x) ) −−−−−−−−−−→ 7
output

• Assignment affects members if done properly:


[a,b,c]=[1,0.2,3]
1
print(a) −−−−−−−−−−→ 0.2
print(b) output
29
• Index ranges retrieve list fragments: x[j:k] = elements in x having index in {j, ..., k−1}.
– Negative ranges work the same way: x[-j:-k] stands for indices from −j (incl.) to −k (excl.).
– Open ranges work intuitively as well.
x=[1, -4.1, "a", "c", 3, 56.3, -10.1]
["a"]
print ( x[2:3] )
["a", "c", 3]
print ( x[2:5] )
−−−−−−−−−−→ ["c",3,56.3]
print ( x[-4:-1] ) output [1, -4.1]
print ( x[:2] )
[3, 56.3, -10.1]
print ( x[4:] )

• We can also append elements (add them to the end of a list):


list1=[-1,2,3,0.3,"a"]
list1.append(34) −−−−−−−−−−→ [-1,2,3,0.3,'a',34].
print ( list1 ) output

• list.insert(k,member) places member in index k (thereby raising len(list) by one):


list1=[-1,2,3,0.3,"a"]
list1.insert(1,"b") −−−−−−−−−−→ [-1,'b',2,3,0.3,'a']
print ( list1 ) output

because "b" was placed in index 1 (hence pushing the rest of the list one place to the right).
• Needless to say, list.insert(0,· · · ) is the same as prepending, i.e. placing · · · at the beginning.
• extend() concatenates lists:
list=[1,2,3,4,5,6]
list.extend([1,7,8]) −−−−−−−−−−→ [1,2,3,4,5,6,1,7,8].
print ( list ) output
30
• Deletion can be done as follows:
– remove eliminates the first occurrence of a specified member:
list1=[1,2,3,"a",5,"b","a"]
list1.remove("a") −−−−−−−−−−→ [1, 2, 3, 5, 'b', 'a']
print(list1) output

and we would have to call remove() again to eliminate all occurrences of "a".
– pop() removes a specified index or removes the last item if no index is specified:
list.pop(1)
print(list) [1, 3, 5, 'b', 'a']
list.pop() −−−−−−−−−−→ [1, 3, 5, 'b']
output
print(list)

– del removes a specified index:


del list[2]
print(list) −−−−−−−−−−→ [1, 3, 'b']
output

or deletes a whole list by writing, e.g. del list.


– clear does precisely what it name indicates:
list.clear()
print(list) −−−−−−−−−−→ []
output

31
• clear is equivalent to successive applications of pop, remove or del:
list=[1,2,3,4]
print(list)
list.pop()
[1,2,3,4]
print(list)
[1,2,3]
list.pop()
[1,2]
print(list) −−−−−−−−−−→ [1]
list.pop() output
[]
print(list)
0
list.pop()
print(list)
print(len(list))

list=[1,2,3,4]
print(list) [1,2,3,4]
list.clear() −−−−−−−−−−→ []
print(list) output 0
print(len(list))

Thus an empty list [ ] has length zero but is still a list, and can be replenished again:
list.append(1)
print(list) −−−−−−−−−−→ [1]
output

• sort does what its name implies to numerical lists:


list1=[1,2,3,4]*3
list1.sort() −−−−−−−−−−→ [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
print(list1) output
• Assignment, e.g. list2=list1 translates in passing a reference (a place within memory) from
list1 to list2. This means that any change made to list1 is automatically made to list2:

list1=[1,2,3,4]
list2=list1
[1, 2, 3, 4, 5]
list1.append(5) −−−−−−−−−−→ [1, 2, 3, 4, 5]
print(list1) output
print(list2)

• In order to copy a list to another list that will be henceforth independent of it, use copy():
list1=[1,2,3,4]
list2=list1.copy()
[1, 2, 3, 4, 5]
list1.append(5) −−−−−−−−−−→ [1, 2, 3, 4]
print(list1) output
print(list2)

• Operator + concatenates the given lists (same as extend explained before):


list1=[1,2,3,4]+[-1,-2,-3]
print(list1) −−−−−−−−−−→ [1, 2, 3, 4, -1, -2, -3]
output

• And by this logic, “multiplying” a list by an integer concatenates copies of it:


list1=[1,2,3,4]*3
print(list1) −−−−−−−−−−→ [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
output

• reverse does what it name implies to any list. Check this week’s Jupyter notebook.
32
Tuples
• Tuples (...) are unchangeable arrays, i.e. cannot be modified after their creation. Hence
– adding (append, extend, insert...),
– removing (remove, pop, del, clear...),
– or changing individual members,
are not possible in any given tuple.
• They do allow extraction of portions, but the extracted output is a new tuple.
• They allow duplicates, e.g. (a,a,b) makes sense and is different from (a,b).
• From Python 2.6 onward, they are indexed, i.e. members can be located with an index; index
ranges, negative indexing etc works just like in lists.
• Membership of a given element in a tuple can be checked (with in).
• Why are they useful and when are they advantageous?
– Faster to process and occupy less memory space than lists (they are more optimal).
– They allow us to format strings.
– They can be used as keys in dictionaries (unlike lists).

33
• Tuples are written:
– with round brackets,
tuple1 = ("a",1,2,3,0,"hello there")
−−−−−−−−→ <class 'tuple'>
print(type(tuple1)) output
– or with no brackets at all (this is specific to tuples and does not apply to lists):
tuple2 = "a",1,2,3,0,"hello there"
−−−−−−−−→ <class 'tuple'>
print(type(tuple2)) output
• You can create any array with only one item. However, in such conditions a tuple needs a comma
at the end to be recognised as a tuple (whereas a list does not):
list 1=[1]
maybe tuple 1=(1)
<class 'list'>
maybe tuple 2=(1,)
−−−−−−−−→ <class 'int'>
print(type(list 1)) output <class 'tuple'>
print(type(maybe tuple 1))
print(type(maybe tuple 2))

Needless to say, the aforementioned item implies you can write (1,) as 1, and it will still be
recognised as a tuple.

34
• You try to change the length or the contents of a tuple, and you get an error:
AttributeError:
tuple1.insert(1,"a") −−−−−−−−−−→ 'tuple' object has no attribute 'insert'
output
AttributeError:
tuple1.append("a") −−−−−−−−−−→ 'tuple' object has no attribute 'append'
output
AttributeError:
tuple1.pop() −−−−−−−−−−→ 'tuple' object has no attribute 'pop'
output
TypeError:
tuple1[0]=3 −−−−−−−−−−→ 'tuple' object does not support item assignment
output
TypeError:
del tuple1[0] −−−−−−−−−−→ 'tuple' object doesn't support item deletion
output
AttributeError:
tuple1.remove("a") −−−−−−−−−−→ 'tuple' object has no attribute 'remove'
output

and same goes for extend, clear, etc.


• But using function list we can copy the tuple to a list and perform the above on the latter:
list1=list(tuple1)
print(list1) −−−−−−−−−−→ ['a', 1, 2, 3, 0, 'hello there']
output

• The same goes the other way with function tuple (albeit losing of course the ability to modify):
list1=[1,2,3]
mytuple=tuple(list1) −−−−−−−−−−→ (1, 2, 3)
print(mytuple) output
35
• Operator + (hence “multiplication” by integers) still works for concatenation, just like in lists:
tuple1=(1,2,3)
tuple2="a","b",4 (1, 2, 3, 'a', 'b', 4)
tuple3=tuple1+tuple2 −−−−−−−−−−→
print(tuple3) output ('a', 'b', 4, 'a', 'b', 4)
print(2*tuple2)

• Membership is still checked easily:


if 1 in tuple1: 1 is in this tuple
print("1 is in this tuple") −−−−−−−−−−→
print("a" in tuple1) output False

• Indexing is the same as in lists in recent versions of Python, e.g. print(tuple1[0]) yields 1.
• There is a function useful to tuples and lists, namely count:
tuple 1="a","b",1,2,3,44,3 1
print(tuple 1.count("a")) −−−−−−−−−−→
print(tuple 1.count(3)) output 2

• Multiple member assignment works just like in lists:


u1,u2,u3,v,w,x,y=tuple 1
a
print(u1) −−−−−−−−−−→ 2
print(v) output

• Index ranges and len work exactly the same as in lists:


print(tuple 1[1:4]) ('b', 1, 2)
print(len(tuple 1)) −−−−−−−−→ 7
output
36
Sets
• Sets { ... } are changeable, unordered and unindexed arrays, i.e. can be modified, have no
fixed order in which the items appear and items cannot be accessed with an index.
• Duplication is not allowed, i.e. every member is counted once:
set 1 = {1,2,"a",2}
print(set 1) −−−−−−−−−−→ {1, 2, 'a'}
output

• Addition of one new element is carried out with add:


set 1.add(3)
print(set 1) −−−−−−−−−−→ {3, 1, 2, 'a'}
output

• Addition of more than one element is carried out with update and either curly or square brackets:
set 1.update({3,4,5})
print(set 1) {1, 2, 3, 'a', 4, 5}
set 1.update([4,5,6,"u"]) −−−−−−−−−−→ {1, 2, 3, 'a', 4, 5, 6, 'u'}
output
print(set 1)

• len and remove work same as in lists, as long as we are aware that duplication is not possible here:
set 1.remove(1)
{2, 3, 'a', 4, 5, 6, 'u'}
print(set 1) −−−−−−−−−−→ 7
print(len(set 1)) output

• Just like in lists, clear empties and del deletes completely. Same syntax (set.clear(), del set).
37
• discard works like remove, but if the element was originally absent, there is no error message:
set 1.discard(1000)
print(set 1) −−−−−−−−−−→ {2, 3, 'a', 4, 5, 6, 'u'}
output

• Union and intersection work just like one would expect in any two sets:
set 1={1,2,3,"a",4}
set 2={2,4,"a","b",-1} {1, 2, 3, 4, 'a', 'b', -1}
print(set 1.union(set 2)) −−−−−−−−−−→ {2, 'a', 4}
output
print(set 1.intersection(set 2))

• Functions max and min are shared by numerical lists, tuples and sets:
set 1={1,2,3,-4}
list 1=[1,2,7,-5]
tuple 1=(0.1,1,2,11) 3
7
print(max(set 1)) 11
print(max(list 1)) −−−−−−−−−−→ -4
output
print(max(tuple 1)) -5
print(min(set 1)) 0.1
print(min(list 1))
print(min(tuple 1))

• Other functions you can work on (see the Jupyter notebook for this week):
copy() difference() difference update() isdisjoint()
issubset() issuperset() update() symmetric difference()
symmetric difference update() pop() intersection update()
38
Dictionaries (short introduction, year 20/21)Older, fuller version in the next three pages
• A fourth data array type, written with curly brackets { key 1:value 1,key 2:value 2,...}, and
– indexed, i.e. there exists a construct aimed at locating elements;
– unordered, i.e. order is irrelevant (despite being indexed);
– changeable, i.e. can have elements modified, added or removed;
• Allows us to store values of different types (str, int, float, arrays, even other dictionaries).
• Data are stored linked to a key; an association key : value is created for every stored element.
• Example: assume we want to keep tabs of famous films
mydict 1 = {
{ "title" : "The Grapes of Wrath", }
these are the keys "director" : "John Ford", these are the values
"year" : 1938
}

• We will not study these in detail this year. You can try these separate lines of code yourselves:
x=mydict 1.get("year") del mydict 1["director"]
mydict 1["year"]=1940 print(x) print (mydict 1)
print(mydict 1) y=mydict 1.get("country","nothing") print (len( mydict 1 ))
print(mydict 1["director"]) print(y) print (mydict 1.keys())
print("country" in mydict 1.keys()) print (mydict 1.values())

39
%JDUJPOBSJFT
• 4P GBS XF IBWF TFFO UISFF UZQFT PG EBUB BSSBZT MJTUT UVQMFT BOE TFUT ăFSF JT B GPVSUI UZQF OBNFMZ
EJDUJPOBSJFT XSJUUFO XJUI DVSMZ CSBDLFUT & F2v R,pHm2 R-F2v k,pHm2 k-XXX' BOE
m VOPSEFSFE JF PSEFS JT JSSFMFWBOU
m DIBOHFBCMF JF DBO IBWF FMFNFOUT NPEJđFE BEEFE PS SFNPWFE
m JOEFYFE JF UIFSF FYJTUT B DPOTUSVDU BJNFE BU MPDBUJOH FMFNFOUT
• %JDUJPOBSJFT BSF EBUB TUSVDUVSFT BMMPXJOH VT UP TUPSF WBMVFT PG EJĈFSFOU UZQFT bi` BMi 7HQi
BSSBZT FWFO PUIFS EJDUJPOBSJFT 
• %BUB BSF TUPSFE MJOLFE UP B LFZ BO BTTPDJBUJPO F2v , pHm2 JT DSFBUFE GPS FWFSZ TUPSFE FMFNFOU
• "T TBJE BCPWF FMFNFOUT BSF OPU PSEFSFE ZFU UIFZ DBO CF MPDBUFE EVF UP UIF BCPWF BTTPDJBUJPO
• &YBNQMF BTTVNF XF XBOU UP LFFQ UBCT PG GBNPVT đMNT
Kv/B+i R 4 &
iBiH2 ! ]iBiH2] , ]h?2 :`T2b Q7 q`i?]- "
/B`2+iQ` ]/B`2+iQ`] , ]CQ?M 6Q`/]- UIFTF BSF UIF
v2` BSF UIF LFZT ]v2`] , RNj3 WBMVFT
'


• ăF BCPWF EBUB JT XSPOH CFDBVTF UIF đMN XBT SFMFBTFE JO  ăF XBZ UP DPSSFDU UIJT JT UIF TBNF
XF XPVME VTFE XJUI JOEJDFT JO B MJTU
Kv/B+i R(]v2`])4RN9y
T`BMiUKv/B+iV

ZJFMEJOH &^iBiH2^, ^h?2 :`T2b Q7 q`i?^- ^/B`2+iQ`^, ^CQ?M 6Q`/^- ^v2`^, RN9y'
• #Z UIF TBNF QSJODJQMF
Kv/B+i k 4 &
]iBiH2], ]1`b2`?2/]-
]/B`2+iQ`], ]ai2p2M aTB2H#2`;]-
]v2`], RNdd
'
XPVME BMTP OFFE UP CF DIBOHFE VTJOH JOEFYJOH Kv/B+i k(]/B`2+iQ`])4].pB/ GvM+?]
• 8F BDDFTT JUFNT CZ XBZ PG UIF TBNF JOEFYJOH QSJODJQMF PS CZ VTJOH ;2iUV

T`BMiUKv/B+i R(]/B`2+iQ`])V −−−−−−−−−−→ ^CQ?M 6Q`/^


PVUQVU
t4Kv/B+i RX;2iU]v2`]V
T`BMiUtV −−−−−−−−−−→ RN9y
PVUQVU
t4Kv/B+i RX;2iU]+QmMi`v]-]MQi?BM;]V
T`BMiUtV −−−−−−−−−−→ ]MQi?BM;]
PVUQVU

XIFSF UIF TFDPOE WBMVF JO ;2i JT UIF WBMVF UP CF SFUVSOFE JG UIF LFZ JT OPU GPVOE

• H2MU Kv/B+i k V Kv/B+i kX+H2`UV BOE Kv/B+i kX+QTvUV XPSL KVTU MJLF UIFZ XPVME JO B MJTU

• 8F DBO EFDJEF XF OP MPOHFS XBOU Kv/B+i k UP IBWF B ]/B`2+iQ`] TMPU
/2H Kv/B+i k(]/B`2+iQ`])
&^iBiH2^, ^1`b2`?2/^- ^v2`^, RNdd'
T`BMi UKv/B+i kV −−−−−−−−→
PVUQVU
ăJT DBO BMTP CF EPOF XJUI Kv/B+i kXTQTU]/B`2+iQ`]V DIFDL UIJT ZPVSTFMWFT 
• 8F DBO DIFDL LFZ NFNCFSTIJQ XJUI BM
B7 ]v2`] BM Kv/B+i k,
T`BMiU]AM/22/- ^v2`^  F2v BM i?Bb /B+iBQM`v]V −−−−−−−−−−→ AM/22/- XXX
PVUQVU

• 8F DBO BEE B OFX NFNCFS CZ BEEJOH B OFX LFZ BOE MJOLJOH JU UP B OFX WBMVF
Kv/B+i 4 &]iBiH2], ]1`b2`?2/]- ]/B`2+iQ`], ].pB/ GvM+?]- ]v2`], RNdd'
Kv/B+i(]+QmMi`v]) 4 ]la]
T`BMi UKv/B+iV

PVUQVU

!
&^iBiH2^, ^1`b2`?2/^- ^/B`2+iQ`^, ^.pB/ GvM+?^- ^v2`^, RNdd- ^+QmMi`v^, ^la^'

• ăF LFZT PG Kv/B+i DBO CF BDDFTTFE WJB Kv/B+iXF2vbUV %JUUP GPS Kv/B+iXpHm2bUV


T`BMiUKv/B+iXF2vbUVV /B+i F2vbU(^iBiH2^- ^/B`2+iQ`^- ^v2`^- ^+QmMi`v^)V
T`BMiUKv/B+iXpHm2bUVV −−−−−−−−−−→ /B+i pHm2bU(^1`b2`?2/^- ^.pB/ GvM+?^- RNdd- ^la^)V
T`BMiU]v2`] BM Kv/B+iXF2vbUVV PVUQVU h`m2


Exercises
• Build a function reverse tuple that takes a tuple as an argument and returns the same tuple in
reverse order. Test it in a file named reverse tuple.py.
• In a file named replace array.py, build functions taking any list (or tuple) and:
– replacing a given member (provided via index) by a new one.
– replacing all occurrences of a given member (provided via its value) by a new one.
– returning the same array but with two indexed elements swapped.
You can use list.count ( member ) or in for the second item (or some alternative trick).
• Build a recursive function taking two numerical lists (or tuples) as inputs and returning their sum:
}
x = ( x1 , . . . , x n )
7−→ x + y = ( x1 + y 1 , . . . , x n + y n )
y = ( y1 , . . . , y n )
Test this function in a file named array sum 1.py.
• Build a function complement that computes the complement of a set S in a larger set X, i.e. the
set of elements in X that are not in S. The function needs to check that S is indeed a subset of
X. Use only the functions mentioned in this lecture.

41
• Write a program taking two arrays x = (x1, . . . , xn), y = (y1, . . . , yn) by keyboard input and
returning their dot product: ⟨x, y ⟩ = n i=1 xi yi . We have not seen loops yet, so you need to

program your function recursively. It will be much easier once we have seen loops.
• Given two vectors in the plane, x = (x1, x2), y = (y1, y2), the cosine of their angle is
⟨ x, y ⟩ √
cos \
x, y = where ∥(a, b)∥ = (a, b) , (a, b) is the norm of (a, b) ,
∥ x∥ ∥ y ∥
and ⟨·, ·⟩ is the dot product defined above.
y
y2
x2 \
x, y
x

y1 x1

Two vectors are perpendicular if the cosine of their angle is 0, and collinear if their cosine is ±1.
Write a program taking two vectors as input, returning their cosine as an output, and deciding
whether they are perpendicular, collinear (specifying if they are so in the same direction or in
opposite directions), or neither. Set a tolerance < 10−14; anything having a smaller absolute
value is considered to be zero.
42
4. Loops
• The purpose of loops is to repeat one or more lines of code, several times. Let us give an example
2
of why they will become necessary. Suppose we are building the sum
∑ 1:
2
n=1 n
x=0
n=0

n = n+1
incr=1./(n**2)
x = x+incr

n = n+1
incr=1./(n**2)
x = x+incr

writing these 3 lines twice is neither necessary nor advisable, but it is not too detrimental either.
20 200
∑ 1 2000
But what if we need 1 , ,
∑ 1
2 ? Would we write those lines 2000 times?

n 2 n 2 n
n=1 n=1 n=1
k
• And what if we did not even know how many terms k of the sum
∑ 1 we needed, and this
2
n=1 n
number k were to be given by the user who executes the program via input?
• And what if we had to keep asking the user for a reasonable (≥ 1, maybe not too large) value of
k until the input command obtained a good value?

43
• Loops can be either:
– determinate: commands contained in them are executed an a priori known number of times.
– indeterminate: number of iterations is not known by the programmer while they are writing
the code, and will depend on circumstances arising during the execution of the program.
k
• For example, a determinate loop can be used to compute
∑ 1,k = 2, 20, 200, 2000 in the
2
n=1 n
previous page.
k
• However, if we need
∑ 1 after taking k via input, we will need an indeterminate loop because
2
n=1 n
we might not know how many terms of the sum will be requested by the user.
• And if the user keeps making mistakes (e.g. choosing k to be negative or zero) or choosing values
of k that we deem too large (imagine, for instance, we decide set a bound of kmax = 5000 for the
user’s choice of k) but we do not want to stop the program only on account of their bad choices,
we need to keep asking the user for a value within the range {1, . . . , kmax}. That, too, will need
an indeterminate loop.
• We will see two loop statements: for and while. Both can be determinate or indeterminate.

44
The for loop
• Let sequence be any array, i.e.
– a list (seen last week)
– a tuple (seen last week)
– a set (seen last week)
– a dictionary (seen briefly last week)
The syntax of a for loop iterating over sequence is as follows:
for member in sequence:
# perform actions related to member

member can be replaced by any other variable. Unlike other languages, an indexing variable is not
necessarily uniform in its type. For instance:
list1=[1, 3.4, "a"] 1
for x in list1: −−−−−−−−−−→ 3.4
print (x) output 'a'

list2=[1, 3.4, "a","b",-0.2,[1,2]]


for u in list2: 'a'
if type(u)==str: −−−−−−−−−−→ 'b'
output
print (u)

45
• The sequence can also be a tuple:
tuple 1=1, 2, 3, 4, 10, 20, 30, 40 1
for i in tuple 1: 4
if i%3==1: −−−−−−−−→ 10
output
print (i) 40
• or a set:
0
set 1={1,3,7,17}
1
for j in set 1: −−−−−−−−→
output 8
print (j//2)
3
'hello'
for j in {1,2,"u"}:
−−−−−−−−→ 'hello'
print ("hello") output 'hello'
• or even a string:
email = False
for i in "[email protected]":
if i=="@":
email = True
if email: # Another way of writing if email == True
print("This seems to be an email address")
yields the output This seems to be an email address.
• or a dictionary.
46
• The range function saves us the effort of having to write a sequence from 0 to a given number:
0
for i in range(4): 1
print (i) −−−−−−−−−−→ 2
output
3

hence range(4) stands for “the first four non-negative integers, starting at 0”.
• And range(k,n) stands for “the first n non-negative integers after and including k”, i.e.
range(n)==range(0,n):
4
for var in range(4,7):
−−−−−−−−−−→ 5
print(var) output 6

• And range(k,n,step) stands for “the n non-negative integers after and including k, with incre-
ments of step at a time”, i.e. range(n)==range(0,n,1) :
4
for var in range(4,11,3):
−−−−−−−−−−→ 7
print(var) output 10

• Nested loops are also easy to program:


Mary knows Ann
Mary knows David
for i in ["Mary","John"]:
Mary knows Robert
for j in ("Ann","David","Robert"): −−−−−−−−−−→ John knows Ann
print (i," knows ",j) output
John knows David
John knows Robert

47
• All of the above were determinate loops because we knew the loop-defining sequence (and thus
its length). However, sometimes we will be faced with the need to program indeterminate loops:
k=int(input("Write a length for the range "))
for x in range(k):
print(x)

will have an output that will depend on what we write. Test it yourselves.
• Alternatively k could come from another function or computation. Think of possible examples.
• You will find an example in this week’s Jupyter notebook approximating the cosine function with
a finite sum whose number of summands can be decided by the user.
• The routine below allows us to decide how many terms of a certain sum we wish to compute:
s=0 # We need to initialise the sum to zero
k=int(input("Write the maximum value of your summand: "))
step=int(input("Write the increment: "))
for x in range(0,k,step):
s += x # which is the same as s = s+x
print(s)

for instance choosing k=10 and step=3 yields 0+3+6+9=18. Again, these two parameters (k and
step) could be the output of earlier functions instead of keyboard input.
48
• The break statement allows us to stop the loop whenever a condition is met, regardless of whether
all the elements in the sequence have been browsed:
0
for x in (0,1,2,3,"a","b","c",8,9):
1
print (x)
2
if type(x)==str: −−−−−−−−−−→ 3
break output
a
print("x is now ",x)
x is now a

• Here we also store loop variables in a list:


x=[]
for j in range(10):
if j % 8 == 7:
break −−−−−−−−−−→ [0, 1, 2, 3, 4, 5, 6]
output
x.append(j)
print(x)

• The continue statement does not stop the entire loop; it only stops the iteration affected:
x=[]
for j in range(10):
if j % 8 == 7:
continue −−−−−−−−−−→ [0, 1, 2, 3, 4, 5, 6, 8, 9]
output
x.append(j)
print(x)

it only failed to append 7 to x but the loop went on for its entire range.

49
The while loop
• Assume we have a condition. The while loop linked to it will be iterated as long as the condition
holds. The syntax of a while loop iterating over sequence is as follows:
while condition:
# perform actions

• For instance:
i=1
while i <= 4:
print("hello")
is an example of an infinite loop because the condition will always be met.
• This loop, on the other hand, will never be executed because the condition will never be met:
i=1
while i > 4:
print("hello")

• This one, on the other hand, will produce a finite output:


i=1 'hello'
while i <= 4: 'hello'
print("hello") −−−−−−−−−−→ 'hello'
output
i=i+1 'hello'

The i++ present in other languages instead of i=i+1 is not valid in Python. i+=1 is, though.
50
• Example of indeterminate while loop:
age = int( input( "Write your age please "))
while age <0:
print ( "When did ages become negative? ")
age = int( input( "Write a correct age please "))

if we write a proper age in the first instance, the while loop will not be entered.
• We can use operator or in the condition:
age = int( input( "Write your age please "))
while age <0 or age >= 200:
print ( "Ages are neither negative nor unrealistically large ")
age = int( input( "Write a correct age please "))

the loop will be exited when the condition ceases to be met.


• We can also use operator and in the condition; pay attention to parentheses:
age = int( input( "Write your age please "))
k=0
while (age <0 or age >= 200) and k<5:
print ( "Ages are neither negative nor unrealistically large ")
age = int( input( "Write a correct age please "))
k+=1

this routine will be less patient and will only ask for a valid age an additional five times.

51
• What transpires from the above is what is already known (De Morgan’s laws) about Boolean op-
erators and negation of multiple conditions:

not (C1 and C2 and . . . and Cn)= (not C1) or (not C2) or … or (not Cn)
not (C1 or C2 or . . . or Cn) = (not C1) and (not C2) and … and (not Cn)
thus condition (age <0 or age >= 200) and k<5 would cease to be met when either

– age < 0 or age >= 200 ceased to be met, i.e. age >= 0 and age < 200,
– or k<5 ceased to be met, i.e. k>=5.
• break works just like in for:
age = int( input( "Write your age please "))
k=0
while age <0 or age >= 200:
print ( "Ages are neither negative nor unrealistically large ")
age = int( input( "Write a correct age please "))
k+=1
if k>=5:
break

is an alternative to the earlier double condition.


52
• else (which we will also see attached to conditional if) can be used in while to run a block of
code whenever the condition ceases to be met:
age = int( input( "Write your age please "))
while age <0 or age >= 200:
print ( "Ages are neither negative nor unrealistically large ")
age = int( input( "Write a correct age please "))
else:
print ( "Finally!")

The last message will only be shown if the user writes a proper age. Attempts are not counted here
so this will last as long as the user wants it to.

53
Exercises
• Some exercises in the past two weeks could have been solved with loops instead of recursive func-
tions. Identify them and rewrite them using loops instead.
• Write a program sum 1.py doing precisely what was described at the beginning of the lecture: take
k as keyboard input, expect it to be within a valid range (larger than 0 and smaller than a bound
k
pre-fixed by you) and compute
∑ 1 . The larger the k, the closer your returned sum should be
n 2
n=1
∞ 2
to
∑ 1 = π ≃ 1.6449340668482262.
2 6
n=1 n
• In a file named look for type.py, build a function counting the elements of a list (or tuple) until
it finds a member of a given type, in which case your function should return its index.
• Write a routine email input.py that takes an email address by keyboard input and decides
whether or not it is a valid address, based on three facts:
– the presence of at least one alphabetic character at either side of the @ sign;
– the presence of exactly one @ sign;
– the presence of at least one dot (.) in the address.
Hint: strings can be indexed and have their length measured, just like lists or tuples.

54
• The for loops shown in this lecture can be easily converted to while loops. Use a file called
for to while.py to convert a few of them.
• Let a > 0 be a real number. Start with an initial condition x0 > 0. The following sequence
1 a
( )
xn+1 = xn + , n≥0 (1)
2 xn

converges to a, i.e. the larger the value of n, the closer xn is to the square root of a. Write a
function Sqrt that is fed the following data by keyboard input:

– a of course;
– a maximal amount nmax of iterations for (1);
– a tolerance tol such, that any number below tol in absolute value will be considered zero.
Your program will start on an initial condition x0 and will perform (1) until either the number of
iterations exceeds nmax, or the difference in absolute value |xk −xk+1| between two consecutive
iterations is smaller than tol. It will then return the last iteration performed.
• Write a function removing all occurrences of a given element from a tuple, set or list.

55
• Write a program that asks for a positive integer n > 2 and decides whether it is prime or not.
• Write a program that asks for a positive integer n > 2 and returns its decomposition as a product
of prime numbers. For instance, 24 should yield a list [2,2,2,3].
• Write a program asking for a positive integer n and returning a right triangle such as the one below,



 1
 2 3



n 4 5 6


 ... ... ...


... ...

 ⋆ ⋆ ⋆

• Write a program storing course subjects (e.g. Maths, Quantum Physics, Chemistry, etc) in a list,
asks the user for the marks they obtained in each subject, and shows the subjects that the user
needs to resit.

56
5. Conditionals
• The control flow of a program is the order in which its individual instructions are executed.
• Control flow is usually directed downwards vertically, but sometimes this needs to be altered–
notably, by control structures or control flow structures allowing us to modify the control flow
without breaking it or bifurcating it. These control structures can be:
– loops: for and while, which we have already seen;
– conditionals: if - else / elif and exception handling: try - except.

Loop: Conditional:
control flow is unidirectional and control flow will only break or bifurcate if options
unbroken but it may become infinite fail to be mutually exclusive and all-emcompassing

57
• The Python if conditional is similar to that of other languages (C++, etc). Its syntax is:
if condition:
# set of instructions in case condition is met

• elif tries to catch the flow if it did not satisfy the conditions in the previous if or elif keywords:
if condition 1:
# set of instructions in case condition 1 is met
elif condition 2:
# set of instructions if condition 2 is met but condition 1 is not
elif condition 3:
# set of instructions if condition 3 is met but condition 1,2 are not
...

• The else keyword catches the flow if none of the preceding conditions were met:
if condition 1:
# set of instructions in case condition 1 is met
elif condition 2:
# set of instructions if condition 2 is met but condition 1 is not
...
else:
# set of instructions if none of the preceding condition 1,2,... are met

58
• The cumulative nature of elif and else with respect to previous conditions, means for instance
that the following block of code:
x=int(input("Write an integer x"))
y=int(input("Write another integer y"))
if x<y:
print("x is smaller than y")
elif x==y: (block 1)
print("they are both equal")
elif x>y:
print("x is larger than y")

yields the same output as


x=int(input("Write an integer x"))
y=int(input("Write another integer y"))
if x<y:
print("x is smaller than y")
elif x==y: (block 2)
print("they are both equal")
else:
print("x is larger than y")

because in (block 1) we carefully chose conditions in the if and elif sub-blocks in such a way,
that the final elif caught the only remaining potential option (just like else did in (block 2)).
• Try erasing the first elif in (block 1) and you will realise the program does nothing in the event
of having x=1 and y=1.
59
• And if we had programmed our second block differently, say only with one if and one else, our
options would be less exhaustive:
x=int(input("Write an integer x"))
y=int(input("Write another integer y"))
if x<y:
print("x is smaller than y")
else:
print("x is larger than or equal to y")

• If we only want to use else in this program while keeping all options (x<y, x>y, x==y) detectable,
then we can nest if - else blocks:
x=int(input("Write an integer x"))
y=int(input("Write another integer y"))
if x<y:
print("x is smaller than y")
else:
if x == y:
print("they are both equal")
else:
print("x is larger than y")

60
• Logical clauses attached to the if and elif keywords are amenable to the same and, or operators
we saw in while loops last week, hence to the same rules (De Morgan’s laws) seen in page 49:
not (P1 and P2 ) = (not P1 ) or (not P2 ) , not (P1 or P2 ) = (not P1 ) and (not P2 ) .
• For instance
x=int(input("Write an integer x>1")) x=2
y=int(input("Write another integer y<-2")) y=0
'Wrong numbers'
if x <= 1 or y >= -2: −−−−−−−−→
output
print("Wrong numbers")

and any “good” choice, e.g. x=2, y=-3, yields no output and keeps the control flow going down.
• And
x=int(input("Write an integer x>1"))
y=int(input("Write another integer y<-2"))
if (x <= 1 and y < -2) or (x > 1 and y >= -2):
print("One of the numbers is wrong")
would still be non-exhaustive with regards to the options (you can find examples of this).

61
• This program recognises palindromes, i.e. words with symmetric spelling. Note that strings can
be indexed ([ ]), measured (len) and concatenated (+) just like tuples or lists:

def is palindrome ( string ):


def inverse str ( string ):
inverse word = inverse str( string )
inv =""
index = 1
counter = len(string)
for i in range( len( string ) ):
index = -1
if inverse word[i] != string[i]:
while (counter >=1 ):
break
inv += str(string[index])
index += 1
index -= 1
if index < len( string ):
counter -= 1
return False
return inv
return True

For instance, is palindrome ( "MADAM" ) should yield True and is palindrome ( "ADAM" )
should yield False. Also in the presence of the return statement no else is needed in
is palindrome because a False output would leave the function altogether and the other option
would not be explored.

62
6. Exception handling
• Assume control flow is interrupted by an unexpected error. For instance,
# other lines of code
40 n = int(input("Write the dividend")) n=2, m=0 ZeroDivisionError:
41 m = int(input("Write the divisor")) −−−−−−−−−−→
output division by zero
42 print ("The quotient n/m equals: ", n/m)
# other lines of code

if the rest of program can function at least partially without the need for n/m, we would like it to.
However, if we executed the program as it is, it would end in line 42 and return the exception,
i.e. the error type ZeroDivisionError described above.
• A way to do so is by means of exception catching. If we know division by zero is a serious risk,
we can amend the program to “catch” this potential malfunction and move on in case it happens:
# other lines of code
n = int(input("Write the dividend"))
m = int(input("Write the divisor"))
try: n=2, m=0
−−−−−−−−→ We cannot divide by 0
print ("The quotient n/m equals: ", n/m) output
except ZeroDivisionError:
print ("We cannot divide by 0")
# other lines of code

and the program moves on, taking the potential exception into account if well programmed.
63
• We can also leave it as a generic exception (i.e. remove the ZeroDivisionError from the above
block) if we are not sure what potential error can arise, e.g.
# other lines of code
n = int(input("Write the dividend"))
m = int(input("Write the divisor"))
try: n=2, m=0
print ("The quotient n/m equals: ", n/m)
−−−−−−−−−−→
output
We cannot divide 2 by 0
except:
print ("We cannot divide ", n, " by ", m)
# other lines of code

but the ideal scenario is being always in control of the potential exceptions.
• Same way this would be correct:
try:
print(z)
except:
print("We could not print z")

but this is better (see https://docs.python.org/3/library/exceptions.html for a list):


try:
print(z)
except NameError:
print("We could not print z because you had not defined it first")

64
• The following block of code keeps asking for two valid numbers until both of them are the right
value (i.e. can be converted to int automatically):
while True:
try:
x1 = int(input( "write the first number" ))
x2 = int(input( "write the second number" ))
break
except ValueError:
print("The values are not numerical, please try again")

When the flow arrives to while True: it prepares for a (potentially) infinite loop. If it gets to
break, that means x1 and x2 were read correctly, hence it will exit without reading the except.
Thus, if we input something that is not numerical in the first number, x1=a, the flow will step
directly to the except message asking us to try again. If we also want to divide x1 by x2, we can
add another except:
while True:
try:
x1 = int(input( "write the first number" ))
x2 = int(input( "write the second number" ))
print("the division of x1 by x2 is"+str(x1/x2))
break
except ValueError:
print("The values are not numerical, please try again")
except ZeroDivisionError:
print("You're asking me to divide by zero")

65
• We can make sure we exit the try - except clause regardless of whether results are satisfactory:
def divide ( ):
try:
x1 = int(input( "write the first number" ))
x2 = int(input( "write the second number" ))
print("the division of x1 by x2 is"+str(x1/x2))
except ValueError:
print("The values are not correct")
except ZeroDivisionError:
print("You're asking me to divide by zero")
finally:
print("we've finished our calculation")
divide( )

regardless of whether or not the code catches any of the two exceptions, it will always execute the
finally line. If anything goes wrong, it will not perform 100% of the try block, but it will always
tell us "we've finished our calculation" and move on.

• finally is useful for instance when we need to work with external files that will have to be closed
after reading or writing on them – this will be seen in a few weeks.

• else can be written instead of finally if there has been no exception; think of possible scenarios
for this.
66
• We can also customise exceptions. Assume we want to compute square roots using the function
Sqrt ( a, nmax, tol ) programmed last week:

def sqrt ( number ):

if number < 0:
raise ValueError ("The number cannot be negative if you want to compute its square root")
else:
return Sqrt( number, 1000, 1.e-15)

num1 = float( input( "Write a number: " ))


try:
print( "The square root of "+str(num1)+" is "+str(sqrt(num1)))
except ValueError as NegativeNumber:
print (NegativeNumber)

print ("Program goes on")

What the above does, is throw a customised exception. Input num1=-1 would return
The number cannot be negative if you want to compute its square root

because that is the label of the ValueError exception in our program.

67
Exercises
• Some of the programs you have practiced on in the past three weeks could have been improved
with a block of code asking for the user input up to a fixed number of times, or until the user
writes something that makes sense. Implement that modification in some of those programs.
• This code is waiting for you to make it work properly:
x=int(input("Write an integer x>1"))
y=int(input("Write another integer y<-2"))
if x <= 1 or y >= -2:
print("Both are wrong numbers")
elif x > 1:
print("x is correct but y is not")
elif y <-2:
print("y is correct but x is not")
else:
print("both numbers are as requested")

• Define a function max out of three expecting three numbers and returning their maximum.
• Define a function vowel taking a character and returning True if it is a vowel, and False otherwise.
• Write a single function that does the exact same task as the combination of reverse string and
is palindrome in page 56, using only one loop and one conditional block.
68
• The following is the graph of a certain mathematical function y = f (x):

it also contains an example of a closed interval [x0, x1] , i.e. a set of points in the x-axis such that
x0 ≤ x ≤ x1 (extremes inclusive). Write a function verify signs taking two extremes x0, x1
of any closed interval and returning the number of changes in sign experienced by the function
within that interval. For instance, the interval in the picture would return 0 because the function
does not cross the x axis even once within the interval. x0=-10, x1=10 would return 3, etc.
Make sure your function asks for x0 and x1 until they are both numerical and ordered: x0<x1.
69
7. Inputting, outputting, storing and retrieving data
• We have already seen two built-in functions: print and input:

print ("Hello") −−−−−−−−→ 'Hello'


output
x=[1,2,"a"]
−−−−−−−−→ [1,2,"a"]
print (x) output

Write a number
x=float(input("Write a number")) −−−−−−−−→
output

n=int(input()) −−−−−−−−→
output

• We also know the meaning of \n (line feed) and \t (horizontal tab).


• An option in print is end, which by default is \n but can be changed at will:
print("Hello and", end = " ")
print("goodbye") −−−−−−−−−−→ Hello and goodbye
output
print("Hello and") Hello and
print("goodbye") −−−−−−−−−−→ goodbye
output

70
• We can concatenate and index strings just like we did with other types of arrays,
x="Hello "
y="World"
z=x+y −−−−−−−−−−→ Hello World
output
print(z)

x = "Hello World"
print(x[2:5]) −−−−−−−−−−→ llo
output

• We can split them,


x = "Hello World Today"
−−−−−−−−→ ['Hello', 'World', 'Today']
print(x.split()) output
• Replace elements in them,
x = "Hello World Today"
−−−−−−−−→ Hello, World, Today
print(x.replace(" ", ", ")) output
• Or check membership:
string = "Mary had a little lamb"
fragment = "ad a l" True
print (fragment in string) −−−−−−−−→ False
output
print ("lamb" not in string)

71
• We have already seen in examples that we can convert other types (e.g. numbers) to string form,
frag = str(14//3)
print("Integer division of 14 by 3 is "+frag) −−−−−−−−−−→ Integer division of 14 by 3 is 4
output

• or print string blocks separated by commas; any of the following has the same output as above:
print ("Integer division of 14 by 3", "is", 14//3)

print ("Integer division of 14 by 3", "is", str(14//3))

• Another way to add “mobile” or initially unknown parts of text is by formatting:


frag = str(14//3) frag = str(14//3)
txt = "Integer division of {} by {} is {}" and txt = "Integer division of 14 by 3 is {}"
print(txt.format(14,3,frag)) print(txt.format(frag))

have the same output as the above blocks. The block below allows for digit control:
frag = 14//3
txt = "Int. div. of {} by {:f} is {:.2f}" −−−−−−−−−−→ Int. div. of 14 by 3.000000 is 4.00
print(txt.format(14,3,frag)) output

note the difference between the default number of floating-point decimal digits (chosen for 3)
and the two decimal points specifically chosen for 4.

72
• You can also ensure values are placed where they belong by indexing within the curly brackets:
frag = 14//3
txt = "Int. div. of {0} by {1:f} is {2:.2f}" −−−−−−−−−−→ Int. div. of 14 by 3.000000 is 4.00
print(txt.format(14,3,frag)) output

• and index repetition has the obvious effect:


term = 2
txt = "{0} plus {0} is {1:.3f}" −−−−−−−−→ 2 plus 2 is 4.000
print(txt.format(term,term+term)) output

• We can customise indices in { }, to the expense however of having to define them within format:
string = "{num1}+{num2} = {output}, {message}"
print(string.format(num1 = 2, num2=2, output=4,message="obviously"))

yields 2+2 = 4, obviously. Replacing {output} by {output:.2f} yields 4.00, etc.


• Formatting works well with loops and keyboard inputs:
0+0=0.00
string = "{n}+{m}={out:.2f}" 0+1=1.00
for i in range(3): 1+0=1.00
for j in [0,1]: −−−−−−−−−−→ 1+1=2.00
output
print(string.format(n=i,m=j,out=i+j)) 2+0=2.00
2+1=3.00

73
• Formatting can also be carried out with symbol %:
age = 30
print("John is %d years old" % age) −−−−−−−−−−→ John is 30 years old
output

• Including multiple terms, same as before, and floating-point numbers operate as usual:
age1, age2 = 25, 30
name1, name2 = "Jane", "John" Jane is 25 years old
print("%s is %d years old" % (name, 25)) −−−−−−−−−−→ Their age average is 27.50000
output
print("Their age average is %.5f" % (0.5*(age1+age2)))

the latter of which is the same as below (bear in mind we are just storing values in a str variable):
age1 = 25
age2 = 30
string = "The age average is %.5f" % (0.5*(age1+age2)) −−−−−−−−−−→ Their age average is 27.50000
output
print(string)

• Most common types: %d (integers), %f (floating point numbers) and %s (strings).


• You can also use formatted string literals (also known as f-strings) to format strings in a more
compact way. Check this week’s Jupyter notebook for more examples:
name = "John"
age = 20
city = "Portsmouth"
print( f"Hello, {name}, you are {age} and you live in {city}" )

74
File handling
• Goal: data persistence, i.e. the need to store information produced during the execution of a
program, in order to avoid its loss after the execution ends.
• We have two alternatives:
– external files, which we will see today, and
– databases, which will not be the focus of our course.
• External files, i.e. files other than the .py program manipulating them, can be:
(a) created from the program, i.e. your folder or directory will contain a new file after this step,
(b) opened from the program, i.e. the external file will be ready to be read or written into,
(c) read from the program, i.e. having their content stored or processed,
(d) updated from the program, i.e. written into or having their content modified,
(e) closed from the program, i.e. from here onwards the program will be unable to modify them,
(f ) deleted from the program, i.e. your folder will no longer contain the file after this step.
• They can take place several times in the same program. (c) and (d) can happen simultaneously.
• The tools to work with files are in the io module (https://docs.python.org/3/library/io.html)

75
• Opening and creating a file: we use the command open. Syntax:
variable = open ( "name.extension", options )

– options can be:


∗ "x": creates a file, returns an error message if it already exists.
∗ "w" for write: opens file for writing, creates the file if it does not exist
∗ "a" for append: opens file for adding data, creates the file if it does not exist
∗ "r" for read: opens file for reading, returns an error message if it does not exist
– as well as:
∗ "t" for text, which will be the type you will use this year;
∗ "b" for binary, e.g. images or video files.
– Default values are r (read) and t (text). Thus,
file = open("samplefile.txt") is the same as file = open("samplefile.txt", "rt")

• Closing a file: we use close, e.g. variable.close()

76
• Reading from a file: can be done whenever we have opened it with r, e.g.
– Create a new file, naming it manually created.txt.
– Open that file with an editor, and write anything you want on it.
– Close file manually created.txt.
– Write the following code in a Python program:
myfile = open("manually created.txt", "r")
u = myfile.read()
#sequence of commands
myfile.close()

– if #sequence of commands includes print(u[0]), you obtain the first character in that file.
print(u[1]) will return the second character, etc.

– print(type(u)) yields <type 'str'>; file contents are retrieved in string format.

– print (u) yields the entirety of the contents of myfile.

– u = myfile.read(k), if k is an integer, defines u as the first k characters written in the file.

77
• readlines reads the contents of a file and returns a list of its successive lines. For instance, assume
the contents of your file manually created.txt are
1 2
a b
1.2 hello goodbye

Then readlines reads those three lines and returns a line containing them:
file = open ("manually created.txt")
f=file.readlines()
file.close() −−−−−−−−−−→ ['1 2\n', 'a b \n', '1.2 hello goodbye ']
output
print(f)

thus we can conduct ourselves the way we would in any list:


file = open ("manually created.txt") 1 2
f=file.readlines()
file.close() −−−−−−−−−−→ a b
for x in f: output
print x 1.2 hello goodbye

hence we can split these strings and (whenever possible) convert their parts to float or int:
u=f[0].split()
print(u)
['1', '2']
x=float(u[0])
−−−−−−−−−−→ 1.0
y=float(u[1]) output 2.0
print(x)
print(y)

78
• Writing on a file: opening a file with "w" (i.e. preparing it to write data on it),
– creates it if it did not exist, and
– empties it if it already existed.
For instance, if file samplefile.txt did not exist in our directory, then after the following
file = open("samplefile.txt", "w")
file.write("Hello world!")
file.close()

a new file samplefile.txt appears in the same folder as the .py file, containing "Hello world!".
• The same sequence, however, replacing samplefile.txt by the already-existing
manually created.txt, will erase its contents and replace them as follows:

1 2 Hello world!
a b −−−−−−−−→
1.2 hello goodbye replace
old contents of manually created.txt new contents of manually created.txt
• Appending writes on a file without erasing its previous content, and creates it if it did not exist:
file = open("manually created.txt", "a")
file.write("123") Hello world!123
−−−−−−−−−−→
file.close() output new contents of manually created.txt

79
• Conventions in writing and appending are the same as for print:
file = open("samplefile.txt", "w") 1.00000 2.0000000000
file.write("{:.5f} \t {:.10f} \n".format(1,2)) 0.2000000000 4.00
file.write("{:.10f} \t {:.2f} \n".format(.2,4)) −−−−−−−−−−→ 1, 2.000000 , whatever
file.write("%d, %f , %s\n" % (1,2,"whatever")) output
file.close()
new contents of samplefile.txt

• With the additional function writelines which does exactly what it name says:
1.00000 2.0000000000
0.2000000000 4.00
file = open("samplefile.txt", "a") 1, 2.000000 , whatever
linearray= ["one\n","two", "\t and %.3f" % 3] one
file.writelines(linearray) −−−−−−−−−−→
output two and 3.000
file.close()

new contents of samplefile.txt

(and works equally well if linearray is a tuple instead of a list)


• And is amenable to everything we can do with a list, including shortcuts:
file = open("samplefile.txt", "w") 0 1 2 3 4
file.writelines(["%s\t" % item for item in range(5)]) −−−−−−−−−−→
file.close() output new contents of samplefile.txt

• Again, f-strings can be used in external files. See the Jupyter notebook for an example and
elaborate further examples on your own.
80
Exercises
• Write a program receiving a string and returning a double list
[[ word 1 , frequency 1 ],
[ word 2 , frequency 2 ],
.
.
. .
.
. .
.
.
[ word n , frequency n ]]
collating each character against the frequency with which it occurs in the string.
• Write a program table.py that takes an integer input k, creates an external file k rows.txt (i.e.
its name adapted to k) and writes two columns of numbers in it:
1 1.000

2 -2.000


k 3 4.000
4 -8.000
. .

.
. .
.

• Write a program modifytable.py that takes an integer input k and creates or modifies an external
file k rows.txt by writing two columns of numbers at the end of it,
1 -1.0000000000000000

2 0.5000000000000000


k 3 -0.2500000000000000
4 0.1250000000000000
. .

.
. .
.

with the understanding that any (if at all) data preceding them will not be erased.
81
• Write a program modifytable2.py taking an integer input k, using the "r+" option instead of
"w" or "a" (you can probe into this yourselves) and modifying an already-existing external file
k rows.txt as follows: the last k rows are made up of a concatenation of the columns of the pre-
vious rows, e.g. for k=2 your output should leave 2 rows.txt modified as follows:
1 1.000
2 -2.000
1 -1.0000000000000000
2 0.5000000000000000
1 1.000 1 -1.0000000000000000
2 -2.000 2 0.5000000000000000

• Write a program modifytable3.py that does the same as the above to an already-existing file but
erases duplicate integer indices in the last k rows, i.e. the output for 2 rows.txt after successively
running table.py, modifytable.py and modifytable3.py with k=2 should be
1 1.000
2 -2.000
1 -1.0000000000000000
2 0.5000000000000000
1 1.000 -1.0000000000000000
2 -2.000 0.5000000000000000

• Write a program frequency.py retrieving text from a file and performing a frequency analysis of
its characters. Use a large text and see how close you get to the famous list (commonly referred
to as ETAONRISH from Herbert Zim’s treatise, although the exact order is controversial) used
for deciphering messages in the English language.
82
8. Functions, program structure and module importation
• In other languages (e.g. C or C++) all commands are centralised in a main function (usually
labelled main()) which is automatically called and executed by the program – and which calls all
the remaining functions in that program. For instance, the following program in C
#include <stdio.h>
float sum (float, float);
int main ()
{
printf("The sum of %f and %f is %f", 1., 2., sum(1.,2.));
return 0;
}
float sum ( float a, float b ) {
return (a+b);
}

has two functions: one returning the sum of two numbers, and the main function calling it.
The program could not function without the latter.
• In Python, however, there is a priori no hierarchy between functions nor a need for a main one,
and the control flow carries out all commands – except of course for those in functions, which
are only carried out if the functions are called.
• There is, however, a main function that can be optionally called in Python as well, and will be
useful in a number of cases–and will justify our explanation of module importation.
83
• The following block of code in file hello.py will not write the contents of the main function:
def main():
print("hello!")
−−−−−−−−→ and goodbye
print ("and goodbye") output
contents of hello.py
• Calling function main, of course, solves this:
def main():
print("hello!")
hello!
main() −−−−−−−−→
output and goodbye
print ("and goodbye")
modified contents of hello.py
• A special variable name will categorise what place this .py file occupies within the project
running it – if it is the main file being run, name will have the value " main ":
def main():
print("hello!")
if name == " main ": hello!
main() −−−−−−−−→ and goodbye
output
print ("and goodbye")
modified contents of hello.py

84
• Check the difference when we are compiling another program that imports file hello.py:
def main():
print("hello!")
print ("and goodbye")

if name == " main ": hello!


main() −−−−−−−−−−−−−−−−−→ and goodbye
output of hello.py
if name == "hello":
print("imported externally")
modified contents of hello.py

import hello and goodbye


−−−−−−−−−−−−−−−−−→ imported externally
contents of new file greetings.py output of greetings.py

• Referring to functions in the imported module can be done with a dot (.):
import hello
and goodbye
hello.main() −−−−−−−−−−−−−−−−−→ imported externally
output of greetings.py hello!
modified contents of file greetings.py

• We can customise importations:


import hello as he
and goodbye
he.main() −−−−−−−−−−−−−−−−−→ imported externally
output of greetings.py hello!
modified contents of file greetings.py

85
• Let us illustrate this with a function other than main:
def main():
print("hello!")
def write age( age ):
print("You are",age,"years old")

if name == " main ": −−−−−−−−−−−−−−−−−→ hello!


main() output of hello.py
if name == "hello":
print("imported externally")
modified contents of hello.py

import hello as he
imported externally
he.write age(20) −−−−−−−−−−−−−−−−−→ You are 20 years old
output of greetings.py
modified contents of file greetings.py

• We can obviously trim our outputs in this case:


def main():
print("hello!")

def write age( age ): No output


print("You are",age,"years old")
modified contents of hello.py

import hello as he

he.write age(20) −−−−−−−−−−−−−−−−−→ You are 20 years old


output of greetings.py
modified contents of file greetings.py
86
• Importation can be nested and reference to the main function can always be reproduced by print:
print("This is the first program")
print(" name is ", name )
def main():
print("And main function in first program") This is the first program
if name == ' main ': −−−−−−−→ name is main
main()
output And main function in first program

contents of first program.py

import first program as fp

print("Part of second program") This is the first program


if name == "second program": −−−−−−−→ name is first program
print("Second program called by external") output Part of second program
contents of second program.py

import second program as se This is the first program


name is first program
print("Third program") Part of second program
se.fp.main() −−−−−−−→ Second program called by external
print(" name is ", name ) output Third program
And main function in first program
contents of third program.py name is main

• As a rule of good practice in programming, and in order to have full control of the program(s)
we are working with, we recommend that you always define a main function.

87
• Packages are directories where we store modules that are related to one another.
• They are useful as a means to organise and reuse modules.
• Method to create a package: create a folder containing a file named init .py.
• For example, name a new folder my folder and fill it with the following files:
# Empty file! def add list( list1, list2 ):
list3=[]
for i in range(len(list1)):
list3.append(list1[i]+list2[i])
return list3

contents of init .py contents of vectors.py

if we open another file outside of this folder,


from my folder.vectors import add list

print( add list( [1,2],[3,4] ) ) −−−−−−−−−−→ [4, 6]


output
contents of use module from outside.py

and if we open another file inside the same directory my folder,


from vectors import add list

print( add list( [1,2],[3,4] ) ) −−−−−−−−−−→ [4, 6]


output
contents of use module from inside.py
88
• Assume our folder my folder and/or its modules have more “substance” to them:
def add list( list1, list2 ):
list3=[]
for i in range(len(list1)):
def scalar( number, list1 ):
list3.append(list1[i]+list2[i])
list2=[]
return list3
for i in range(len(list1)):
def subtract list( list1, list2 ):
list2.append(number*list1[i])
list3=[]
return list2
for i in range(len(list1)):
list3.append(list1[i]-list2[i])
init .py return list3 contents of vector and number.py

contents of vectors.py

we now have two functions in vectors.py; we can import only what we need, or we can import
everything with a *:
from my folder.vectors import *
from my folder.vector and number import scalar
[4, 6]
print( add list( [1,2],[3,4] ) ) [-2, -2]
print( subtract list( [1,2],[3,4] ) ) −−−−−−−−−−→ [9.3, 12.4]
print( scalar( 3.1,[3,4] ) ) output
[-2, -4, -5]
print( scalar( -1,[2,4,5] ) )

contents of use module from outside.py

• Subpackages follow the same logic (just keep tabs of your extensions with ., e.g.
from my folder.some subfolder.some module import *).
89
Functions and variable inputs as arguments of functions
• Functions can call other functions as arguments. For instance,
def callingfunction( f, x ):
return f(x+1)
def func1 ( x ):
return x**2 16
def func2 ( x ): −−−−−−−−−−→ 64
output
return x**3
print( callingfunction( func1, 3 ) )
print( callingfunction( func2, 3 ) )

• You can also pass a variable amount of arguments, using the unpacking operator (*) which does
what it name indicates to any list or tuple so that its elements can be passed as different parameters:
def sum (*args):
s = 0
for x in args:
s+=x
6.2
return s
−−−−−−−−−−→
output 9.4
print(sum(1, 2, 3.2))
print(sum(0.1, 0.2, -.9, 10))
thus we can pass any amount of arguments to sum as long as they are numbers.
• On a side note, unpacking operators also work when we are not calling functions:
list1 = ["A", "B", "C", "D", 1, 2.1]
a, *b, c, d = list1 A
print(a) ['B', 'C', 'D']
print(b) −−−−−−−−−−→ 1
output
print(c) 2.1
print(d)
90
• This turns our Sqrt function from Week 4 into one that can be called with one single argument:
def Sqrt ( a, *args ):
nmax =1000
tol=1.e-20
if len(args)>0:
nmax=args[0]
if len(args)>1:
tol=args[1]
x0 = 1
Abs = 1
x=x0
i=0
while Abs >= tol and i<nmax:
X = 0.5* (x+(a/x))
Abs = abs(X-x)
x=X
i+=1
if i>=nmax:
print("max. number of iterations was reached before precision goal was achieved")
return X

– Calling Sqrt(2) is the same as calling Sqrt(2,1000,1.e-20) and both will yield
1.41421356237309492343.
– Calling Sqrt(2,3) is the same as Sqrt(2,3,1.e-20) and only three iterations will be under-
taken, yielding the very imprecise approximation 1.41421568627450966460.
– Sqrt(2,5,1.e-5) packs two elements to args and the output is 1.41421356237468986983.

91
• lambda functions can also be useful: their syntax is:
lambda arguments : output

• They are easily amenable to redefinition, but the onus is on you to check the latest definition:
x = lambda x : x**2
print(x(2))
4
print(x(3))
9
x = lambda t : t**3
8
print(x(2)) −−−−−−−−→
output 27
print(x(3))
7
x = lambda t,u : t+u
3
print(x(3,4))
print(x(1,2))

• And they can be called by other functions just like we did earlier:
def evaluate( func, a ):
return func(a)
9
x = lambda t : t**2 −−−−−−−−→ 1296
output
print(evaluate(x,3))
print(evaluate(lambda z : (z+3)**4,3))

92
Exercises
• Write a function which, given function f (x), returns an approximation of the derivative f ′(x0):

f (x+h)−f (x)
f ′ (x0 ) = lim h
h→0

(x0 , f (x0 ))

tangent line having slope f ′ (x0 )


x0

You cannot compute the above limit in general, but you can approximate it:
f ( x + h ) − f ( x)
f ′ ( x) ≃ , |h| very small.
h
Write all of your modules in a single folder called derivation containing at least the following:
– anything turning this folder into a package;
– derivation.py, containing the main function and plenty of derivation examples;
– functions.py program, containing definitions of functions;
You can test your program on a number of functions whose actual derivatives you know. Think
of how you would implement this in the most simple and comprehensive way. 93
• Write a function trapezium which given any function f (x), defined on a given interval [a, b],
∫b
returns an approximation of the integral a f (x)dx (i.e., the area formed by the region bounded
by the graph of f , the x-axis, and the vertical lines at x = a, b) by means of the trapezium rule:

a b a b
∫b
The shaded region is the actual area a
f The shaded region is the approximation T (f, a, b)

The area of a trapezium (i.e. a quadrilateral with at least two parallel sides) is the sum of the
lengths of those parallel sides, times the distance between them, divided by two.

94
• Write another function also named trapezium, with one particularity: if more arguments are
passed into it than those in the earlier trapezium, then the approximation of the integral will not
be by means of the original trapezium rule, but the composite trapezium rule:

a b a = x0 x1 x2 x3 b = x4
∫b
The shaded region is the actual area a
f Shaded region is approximation Tn (f, a, b) with n = 4

In other words, we divide [a, b] in subintervals of equal lengths, construct smaller trapezia from
them, then add the areas. Needless to say,
– T1(f, a, b) should return the original, simple trapezium rule (shown in the previous page)
– The larger the number n of sub-intervals, the better our approximation is... theoretically.
– However, as n grows, so does the number of operations and thus numerical error propagates.
95
• Remarks on the previous two exercises:
– Your two versions of trapezium (the exclusively simple, and the potentially composite)
should be able to comfortably change the function and the domain [a, b].
– Once you have secured a correct function for the first exercise (i.e. simple trapezium), feel
free to use it as a tool for the second exercise (which consists on dividing the original interval
into subintervals of equal length).
– Avoid using the math package to compute functions. Write all of your modules in a single
folder called integration containing at least the following:
∗ anything turning this folder into a package;
∗ simple.py, devoted to simple trapezium (including plenty of examples);
∗ composite.py defining and implementing composite trapezium to many examples;
∗ a .py program, containing definitions of functions not needing Taylor expansions;
∗ a .py program, containing definitions of functions needing Taylor expansions;
Your trapezium examples of application should include instances of lambda, as well as calls
to functions defined by you elsewhere.

96
• You can test your program on a number of functions and intervals; below are some examples with
their definite integrals, and approximations rounded to 20 exact decimal digits:
– cos x in [1, 3]: area equals
( √ sin 3 −
) sin 1 ≃ −0.70035097674802928455
∫ 10 √ √
– 2 x dx = 43 2 5 5 − 1 ≃ 19.196232984625068815
∫1

2

– −1 x + 1 dx = 2 + arcsinh ( )
(1) ≃ 2.2955871493926380740
∫1 1
– 0 2 dx
x −2
= − √1 arctanh √1 ≃ −0.62322524014023051339
2 2
In the above examples, you would only need a Taylor series expansion for cos x. The rest can be
defined with functions already seen by you.
• Examples of Taylor expansions (along x = 0):
∞ 2k ∞ 2k
– cos x =
∑ k x
(−1) (2k)! – cosh x =
∑ x
(2k)!
k=0 k=0
∞ 2k+1 ∞ 2k+1
– sin x =
∑ k x
(−1) (2k+1)! – sinh x =
∑ x
(2k+1)!
k=0 k=0

∑ xk ∞ k+1
– ex = – ln(1 − x) = − x
k+1 , if −1 < x < 1.

k!
k=0 k=0

97
9. Predefined modules in Python
• We have seen that importation of programs written by ourselves is easy:
def Sqrt ( a, *args ): import sqrt as sq
nmax =1000
tol=1.e-20 x = float(input("Write x"))
if len(args)>0: print("Square root is {:.20f}".format(sq.Sqrt(x)))
nmax=args[0]
if len(args)>1:
tol=args[1]
x0 = 1
Abs = 1
x=x0
i=0
while Abs >= tol and i<nmax:
X = 0.5* (x+(a/x))
Abs = abs(X-x)
x=X
i+=1
if i>=nmax:
print("precision not reached")
return X
Contents of sqrt.py Contents of program using sqrt.py

Running program using sqrt.py and inputting 3 yields 1.73205080756887719318, etc.


• The option to customise module sqrt.py as sq is ours and we can call it anything we want –
obviously modifying the way we invoke function Sqrt from this module, e.g.
import sqrt as whatever entails changing line four to ....format(whatever.Sqrt(x)))
98
• Modules can be collected into libraries according to some commonality (e.g. libraries devoted
to maths modules, to statistics modules, to matrix modules, etc).
• However, if we have to program every single module and library from scratch like we did with
Sqrt, the line between spending time and wasting it becomes blurred.
• With Python, we are lucky to have many modules that are predefined (e.g. programmed by
someone else) but have been tested and checked enough times to be considered nearly default or
intrinsic to the language itself.
• Most of these modules are part of the Standard Library; others are valuable extensions of it.
• You can find the Standard Library modules in https://docs.python.org/3/library/.
Most commonly used Standard Library modules and external libraries:

– the math module – the NumPy library – the random module


– the sys module – the SciPy library – the Astropy library
– the Matplotlib library – the SymPy library – the time module
• You can find manuals on these with help, e.g. by including help(math).

99
The math package
• Contains most of the mathematical functions you will be using, e.g. sqrt. Assume we modify
program using sqrt.py (while leaving our own, user-programmed sqrt.py intact):

def Sqrt ( a, *args ): import math


nmax =1000 import sqrt as sq

# ... Our program ... x = float(input("Write x"))


print("Our square root: :.20f".format(sq.Sqrt(x)))
return X print("Their square root: :.20f".format(math.sqrt(x)))
Contents of sqrt.py Contents of modified program using sqrt.py

Comparison seems to indicate at least 15 correct decimal digits after rounding up:
Write x
Our square root: 1.73205080756887719318
3 −−−−−−−−−−→
output Their square root: 1.73205080756887719318

Write x
Our square root: 1.41421356237309492343
2 −−−−−−−−−−→
output Their square root: 1.41421356237309514547

Write x
Our square root: 3.16227766016837907870
10 −−−−−−−−−−→
output Their square root: 3.16227766016837952279

but most computations using square roots will not require all of our 20 digits.
100
• A few other functions in math (as usual, preceded by math. if you imported it as such):

– math.factorial(x): the already-known x! – tan(x): tan x, tangent of x


– floor(x): ⌊x⌋ = max{k ∈ Z : k ≤ x} – cosh(x): cosh x, hyperbolic cosine of x
– fsum(L): sum of the elements in list L – sinh(x): sinh x, hyperbolic sine of x
– gcd(a,b): greatest common divisor a, b ∈ Z – tanh(x): tanh x, hyperbolic tangent of x
– exp(x): already-known exponential ex – acos(x): arccos x, arccosine of x
– log(x): natural logarithm ln x = loge x – asin(x): arcsin x, arcsine of x
– log(x,b): logarithm in any base logb x – atan(x): arctan x, arctangent of x
– pow(x,y): xy regardless of whether y ∈ Z – acosh(x): arccoshx, hyp. arccosine of x
– cos(x): cos x, cosine of x – asinh(x): arcsinhx, hyp. arcsine of x
– sin(x): sin x, sine of x – atanh(x): arctanhx, hyp. arctangent of x

Where Z stands for the set of all integer numbers.


x, y are assumed, by default to be general real numbers ∈ R (i.e. float types).
101
• Constants too:

– math.e: e ≃ 2.718281828459045 – math.pi: π ≃ 3.141592653589793


– math.inf: ∞ or anything beyond a – math.nan: NAN (Not a number, i.e. not amenable
computable limit, e.g. 1.e1000. to arithmetic, e.g. math.inf-math.inf

• Sometimes we will need to “complete” already-existing functions anyway:


import math

def gcd(*nums):
if len(nums)==1:
return nums[0] −−−−−−−−→ 17
if len(nums)>=2: output
return math.gcd(nums[0],gcd(*nums[1:]))
return 1
print(gcd (578, 221, 255, 85))

which allows us to compute the GCD of any set of integers with variadic arguments.

102
The sys package
• Contains useful functions affecting our interaction with the environment where we call Python.
• For instance, sys.argv is a list which by default contains only one element – the file name:
import sys

def main():
print(sys.argv)
−−−−−−−−−−→ ['program.py']
if name == ' main ': output
main()

program.py called from command


line or Jupyter notebook

• If we now call the function from the command line or Jupyter notebook using more arguments,
In [3] : run "program.py" 1
In [4] : run "program.py" 0.1 hello ['program.py', '1']
−−−−−−−−−−→ ['program.py', '0.1', 'hello']
In [5] : output

JUPYTER NOTEBOOK

Thus if we ask for sys.argv[2] in the second case, we would obtain 'hello', etc. This can be
useful to pass data to the program instead of explicitly asking for it via input from the user.

103
The time package
• As the name indicates, this module contains time-related functions.
• time.time() counts time in seconds since a given epoch (in Unix: January 1, 1970, 00:00:00):
import time
import sqrt as sq
import math

def main():
start = time.time()
squareroot1 = sq.Sqrt(18120915176)
end = time.time()
diff = end - start
print("Our Sqrt: ",diff,"sec") Our Sqrt: 2.002716064453125e-05 sec
−−−−−−−−−−→ math.sqrt: 3.0994415283203125e-06 sec
start = time.time() output current time: 1572802240.834068 sec
squareroot2 = math.sqrt(18120915176)
end = time.time()
diff = end - start
print("math.sqrt:",diff,"sec")
cur = time.time()
print("current time:",cur," sec" )

if name == ' main ':


main()

104
Prologue to the NumPy library: Matrices
• Matrices are 2-dimensional arrays of numbers (entries or coefficients) arranged in rows and columns
a1,1 a1,2 ... a1,j . . . a1,m
 
a2,1 a2,2 ... a2,j . . . a2,m
... ... ... ... ... ...
 
 
A=
 
ai,1 ai,2 ... ai,j . . . ai,m ← row i

... ... ... ... ... ...

 
an,1 an,2 ... an,j . . . an,m

Compact forms of notation are column j
( ) ( ) ( ) ( )
A = ai,j = ai,j = ai,j i = 1, . . . , n = ai,j 1≤i≤n
i,j j = 1, . . . , m 1≤j≤m

• Matr,c(R) = set of r × c matrices having real entries. If r = c the matrices are called square
1 2 3 4
 
( )
1 − 2 0 .1  0 1 −1 9 
A= ∈ Mat2,3 (R), B= ∈ Mat4,4 (R),
0 π 3 0 0 3 4 
0 0 0 4
meaning B is square but A is not and a1,1 = 1, a1,2 = −2, a1,3 = 0.1, b2,4 = 9, . . .
• 1 × n (one-row) or m × 1 (one-column) matrices are called row (resp. column) vectors.
• Individual numbers (on their own or appearing as matrix entries) are called scalars.
105
• Two matrices can be added if they share the same numbers of rows and columns:
 
a1,1 + b1,1 a1,2 + b1,2 . . . a1,m + b1,m
 a2,1 + b2,1 a2,2 + b2,2 . . . a2,m + b2,m 
A + B = (ai,j + bi,j )i,j =
 ... ... ... ... 

an,1 + bn,1 an,2 + bn,2 . . . an,m + bn,m
For instance, the sum of two 2 × 3 matrices yields a 2 × 3 matrix:
( ) ( ) ( ) ( )
1 .1 − 2 0 .4 2 9 .1 0 .7 1 .1 + 2 − 2 + 9 .1 0 .4 + 0 .7 3.1 7.1 1.1
+ = =
0 4 .7 5 .1 4 .8 6 0 .4 0 + 4 .8 4 .7 + 6 5 .1 + 0 .4 4.8 10.7 5.5
• Same applies to inverse operation of + (subtraction), replacing every occurrence of + by −:
( ) ( ) ( ) ( )
1 .1 − 2 0 .4 2 9 . 1 0 .7 1 .1 − 2 − 2 − 9 .1 0 .4 − 0 .7 −0.9 −11.1 −0.3
− = =
0 4 . 7 5 .1 4 . 8 6 0 .4 0 − 4 . 8 4 .7 − 6 5 .1 − 0 .4 −4.8 −1.3 4.7
• The particular case of row or column vectors becomes much simpler:
( 0 1 3 ) + ( 1 2 0 .4 ) = ( 1 3 3 .4 )
( ) ( ) ( )
3 π 3−π
− =
4 −1000 1004
• Matrix addition generalises real number addition, i.e. given two numbers (i.e. two 1 × 1 matrices
minus the brackets), adding them as usual numbers is equivalent to adding them as matrices.

106
• Matrices can be multiplied in a number of ways, but the one “natural” to Linear Algebra (i.e.
representing compositions of linear maps) is the following, provided that the number of columns
of the first matrix equals the number of rows of the second:
 
} c1,1 c1,2 . . . c1,p
A ∈ Matn,m  c2,1 c2,2 . . . c2,p  ∑m
A · B = (ci,j )i,j =
 ... ... ... ...  where ci,j = k=1 ai,k bk,j
B ∈ Matm,p 
cn,1 cn,2 . . . cn,p

• Hence, entry i,j in A · B is the dot product of two vectors: row i in A and column j in B:
...
     
a1,1 a1,2 . . . a1,m b1,1 . . . b1,j . . . b1,p ⋆ ⋆
 ... .
.. . .. .
..   b2,1 . . . b2,j . . . b2,p   ... 
 ai,1 ai,2 . . . ai,m  ·  ... ... ... ... ...
     
= ... ... ci,j ... ... 
 . ... ... 
...   ... ... ... ... ...   
 .. ...

 



an,1 an,2 . . . a1,m bm,1 . . . bm,j . . . bm,p ⋆ ... ⋆
⟨ ⟩
where ci,j = row i in A, column j in A = ai,1b1,j + ai,2b2,j + · · · + am,1bm,j .
• Again, · generalises the usual product in R: for 1 × 1 matrices (a), (b), their product is the same
as real numbers or according to the above definition.

107
• For example, given 3 × 4 and 4 × 2 matrices
1 0 .2
   
1 2 −4 3
 2 1 .3 
A =  0 7 0 .1 6  , B=
4 0 
5 4 .3 − 1 2 .2
11 1
• we cannot multiply B · A because the number of columns of B ̸= the number of rows in A.
• we can however multiply A · B because number of columns of A = number of rows in A:
 
1 · 1 + 2 · 2 + (−4) · 4 + 3 · 11 1 · 0.2 + 2 · 1.3 + (−4) · 0 + 3 · 1
 
A·B = 
 0 · 1 + 7 · 2 + 0.1 · 4 + 6 · 11 0 · 0.2 + 7 · 1.3 + 0.1 · 0 + 6 · 1 

5 · 1 + 4.3 · 2 + (−1) · 4 + 2.2 · 11 5 · 0.2 + 4.3 · 1.3 + (−1) · 0 + 2.2 · 1
 
22. 5.8
=  80.4 15.1 
33.8 8.79
which, unsurprisingly, has
– the same number of rows (3) as the first matrix A,
– and the same number of columns (2) as the second matrix B.
The third number, i.e. the dimension shared by both matrices (4 = col. of A = rows of B) plays
no further role after the multiplication is completed.
108
The NumPy library
• Contains useful tools for several disciplines but particularly for Linear Algebra, i.e. manipulation
of arrays of one or more dimensions (vectors and matrices).
• External to the Standard Library, hence must be imported; see info in https://numpy.org/
• The centerpiece is type numpy.array which, at a first glance, works exactly like a list:
import numpy as np
a = np.array([1, 2, 3, 4]) <class 'numpy.ndarray'>
print(type(a)) −−−−−−−−−−→ 1 4
print(a[0], a[3]) output [5 2 3 4]
a[0] = 5
print(a)

• It can also be converted to a list and there is a range analogue in np.array types::
print(a)
[5 2 3 4]
ll=list(a) −−−−−−−−−−→ [5, 2, 3, 4]
print(ll) output
x = np.arange(3)
[0 1 2]
print(x) −−−−−−−−−−→ <class 'numpy.ndarray'>
print(type(x)) output

• Most importantly, numpy contains functions like those in math, but handles those functions, e.g.
np.cos, np.sin, np.exp much faster than their math counterparts. Hence we strongly recom-
mend using numpy instead of math for special functions and constants.
109
• For float types, we can decide whether we want half, single or double floating-point precision:
import numpy as np
x = np.array([1.1,2], dtype=np.float16)
y = np.array([1.1,2], dtype=np.float32) 1.099609375000000000000000000000
z = np.array([1.1,2], dtype=np.float64) −−−−−−−−−−→ 1.100000023841857910156250000000
print("%.30f" % x[0]) output 1.100000000000000088817841970013
print("%.30f" % y[0])
print("%.30f" % z[0])

• We can also alter data types for integers when it comes to ranges:
– int8 -128 to 127 – int32 -2147483648 to 2147483647
– int16 -32768 to 32767 – int64 -9223372036854775808 to 9223372036854775807
With the understanding that attention will have to be paid if we do not wish to overflow variables:
x = np.array([1.1,2], dtype=np.int16)
y = np.array([1.1,2], dtype=np.int32)
-24288
y[0]=x[0]=500000 −−−−−−−−−−→ 500000
print("%d" % x[0]) output
print("%d" % y[0])

If you are not sure what precision your computer is working with by default,
❞❞❞❞11 int64
❞❞❞❞❞❞❞
print( np.dtype(int) )
❩❩❩❩❩❩❩
int32
❩❩❩❩--

110
• Other ways of converting and presetting precision (for matrices, vectors and numbers) are:
import numpy as np

x = np.float32(2.4)
print("x = %.20f" % x)

X = np.float (2.4)
x = 2.40000009536743164062
print("X = %.20f" % X)
X = 2.39999999999999991118
XX = np.float16(2.4)
print("XX = %.20f" % XX)
XX = 2.40039062500000000000
y = np.int ([10.11, 20.56, -1.2])
y= [10 20 -1]
print("y=",y)
−−−−−−−−−−→ unsigned int y=[ 10 20 255]
print("unsigned int y=", end="") output
y = np.uint8([10.11, 20.56, -1.2])
z= [0 1 2 3]
print(y)
float z= [0. 1. 2. 3.]
z = np.arange(4, dtype=np.int8)
print("z=",z)
[1. 2.]
zz=z.astype(np.float16)
print("float z=",zz)
[1. 2.]
t = np.array([1, 2], dtype='f')
print(t)

u = np.array([1, 2], dtype=np.float16)


print(u)

111
• Special matrices, e.g.
– matrices (including vectors) all of whose entries equal the same element,
3 3
( ) ( ) ( )
1 1 3 3 0 0 0 0
1 1 , , 0 0 0 0 , ( 2 2 2 2 2 )
3 3

– the identity matrix playing the role of a multiplicative neutral element in matrices:
 1 0 ... 0 

0 1 ... 0 ( ) (
1 0 0
)  A · Id = A
1 0
Idn =  .. ..
. .
... .. 
. eg. Id2 = 0 1 , Id3 = 0 1 0 ... and Id · B = B
.. .. 0 0 1  for any A, B
. . ... 1

– or matrices (including vectors) whose entries are random float numbers,


are easy to input as arrays here, e.g.
[[1. 1. 1.]
A = np.ones((2,3))
[1. 1. 1.]]
print(A)
[[0. 0.]
B = np.zeros((2,2))
[0. 0.]]
print(B)
[[4 4 4]
C = np.full((3,3), 4)
[4 4 4]
print(C)
−−−−−−−−−−→ [4 4 4]]
D = np.eye(2) output [[1. 0.]
print(D)
[0. 1.]]
E = np.eye(2,dtype=np.int16)
[[1 0]
print(E)
[0 1]]
F = np.random.random((2,4))
[[0.19851442 0.19231117 0.15831314 0.09988122]
print(F)
[0.63140522 0.5235444 0.2604196 0.95926241]]

112
• Matrix arithmetic:
– ± works by overriding operators +, - or by using the add and subtract functions in numpy:
[[-2. 3. 7.]
x = np.array([[1,2,3],[4,5,6]], dtype=np.float64) [ 7. 7. 14.]]
y = np.array([[-3,1,4],[3,2,8]], dtype=np.float64) [[-2. 3. 7.]
print(x + y) [ 7. 7. 14.]]
print(np.add(x, y)) −−−−−−−−−−→ [[ 4. 1. -1.]
output
print(x - y) [ 1. 3. -2.]]
print(np.subtract(x, y)) [[ 4. 1. -1.]
[ 1. 3. -2.]]

– * or multiply do not correspond to matrix multiplication as defined earlier, but to entrywise


multiplication (the Hadamard product):
[[-3. 2. 12.]
print(x * y) [12. 10. 48.]]
print(np.multiply(x, y)) −−−−−−−−→ [[-3. 2. 12.]
output [12. 10. 48.]]

– / or divide, accordingly, produce Hadamard division:


[[-0.33333333 2. 0.75 ]
print(x / y) [ 1.33333333 2.5 0.75 ]]
print(np.divide(x, y)) −−−−−−−−→ [[-0.33333333 2. 0.75 ]
output [ 1.33333333 2.5 0.75 ]]

113
• Matrix arithmetic:
– Dot products ⟨⋆, ⋆⟩ and matrix multiplication as seen pages ago, need numpy function dot:
import numpy as np
35.0
mat1 = np.array([[3,-2],[1,10]])
mat2 = np.array([[1,4],[3,6]])
35.0
vec1 = np.array([1,2],dtype='f')
vec2 = np.array([11, 12])
[-1. 21.]
# Dot product of vectors: two ways
[-1. 21.]
print(vec1.dot(vec2))
print(np.dot(vec1, vec2))
−−−−−−−−−−→ [ 9 131]
output
# Matrix-vector product: two ways
[ 9 131]
print(mat1.dot(vec1))
print(np.dot(mat1, vec1))
[[-3 0]
print(mat1.dot(vec2))
[31 64]]
print(np.dot(mat1, vec2))
[[-3 0]
# Matrix-matrix product: two ways
[31 64]]
print(mat1.dot(mat2))
print(np.dot(mat1, mat2))

– Needless to say, if dimensions are not compatible (i.e. # columns of the first matrix ̸= # of
rows of second) multiplication is impossible:
mat1 = np.array([[3,-2,1],[1,10,4]]) ValueError:
mat2 = np.array([[1,4],[3,6]]) −−−−−−−−−−→ shapes (2,3) and (2,2)
print(mat1.dot(mat2)) output not aligned

114
Exercises
• Imagine you live in a world without numpy.
– Define a function input vector inputting a vector as a list from keyboard.
– Define a function input matrix inputting a matrix as a list (of lists) from keyboard.
– Your already-known recursive and non-recursive functions array sum can add vectors.
Now do the same for an array subtract.
– Define functions add matrices, subtract matrices compatible with your input vector,
input matrix above.
– Define a function multiply matrix vector compatible with your input vector,
input matrix above for the usual matrix-vector product. Make sure your function
checks compatibility of the two matrices.
– Define a function multiply matrices compatible with your input matrix above for the usual
matrix product. Make sure your function checks compatibility of the two matrices.
– Define a function Hadamard product compatible with your input matrix above for the usual
matrix product. Make sure your function checks row and column number identities.
– Write functions such as the above, but in this case taking matrices and vectors as inputs from
external files.
115
• Imagine you live in a world with numpy but you don’t know all of its features. Every square matrix
has a determinant, which can be computed as follows:
– If the matrix is 2 × 2,
( )
a b
A= c d ⇒ det A = ∥A∥ = ad − bc
– Assume A has n ≥ 3 rows and columns. Choose a row or column, for instance in 4 × 4
1 3 0 4
 
 2 3 0 10 
 0 1 9 7 
1 1 1 2
– The minor of a matrix entry is the determinant of the smaller matrix obtained by deleting the
row and column containing that entry, for instance,
 
1 3 0 4  
 2 3 0 4 3 0 4
3 0 10 
the minor of 2 in 
 0
 would be det  1 9 7  = 1 9 7
1 9 7  1 1 2 1 1 2
1 1 1 2
– The determinant of A equals the sum of the products of each element i,j in the chosen
row/column times its cofactor – i.e. its minor, multiplied by (−1)i+j
116
• For instance, in the above matrix we draw a chessboard-style scheme to determine cofactor sign:
1 3 0 4 + − + −
 
 2 3 0 10  − + − + (−1) · 2 (+1) · 3 (−1) · 0 (+1) · 10
 0 ⇒
1 9 7  + − + −
1 1 1 2 − + − +
• and compute its determinant using smaller determinants, each computed the same way:
1 3 0 4    
3 0 4 1 0 4
2 3 0 10
= (−1) · 2 det  1 9 7  + (+1) · 3 det  0 9 7 
0 1 9 7
1 1 2 1 1 2
1 1 1 2
   
1 3 4 1 3 0
+ (−1) · 0 det  0 1 7  + (+1) · 10 det  0 1 9 
1 1 2 1 1 1
= · · · = 113.

• Compute a program that takes a square matrix, no matter how large, from a previously filled-out
external file and computes its determinant with a recursive function. Make sure your program
also prints out the time taken to perform this task.

117
10 The matplotlib library
• Useful to plot data in all its forms (function graphs, sets of points...)
• External to the Standard Library; see documentation in https://matplotlib.org/index.html
• We will focus on module matplotlib.pyplot whose inner workings are similar to MatLab
(which you will see in semester 2).
• First example:

import numpy as np
import matplotlib.pyplot as plt

# array of equidistant numbers


x = np.arange(-3, 3, 0.1)
# array of their exponentials −−−−−−−−→
output
y = np.exp(x)

plt.plot(x, y)
plt.show()

118
• Making the mesh of this partition larger (from 0.1 to 1.), it becomes apparent that by default the
dots in our plot are joined instead of drawn separately:

x = np.arange(-3, 3, 1.)
y = np.exp(x)
plt.plot(x, y) −−−−−−−−→
output
plt.show()

• To draw these points separately (regardless of shape or colour) we need to write a bit more:

x = np.arange(-3, 3, 1.)
y = np.exp(x)
plt.plot(x, y,'go') −−−−−−−−→
output
plt.show()

119
• Here is a list of the most-often used markers:
Marker Shape Marker Shape
'.' Point '|' Vertical Line
'+' Plus '' Horizontal Line
'o' Circle '^' Triangle upwards
',' Pixel '*' Star
Regular n-polygon rotated by an angle a, with style s:
s=0 : convex regular polygon
's' Square (n,s,a) s=1 : concave (star-shaped) regular polygon
s=2 : an asterisk (limit case of s=1)
s=3 : a circle and n, a are ignored
• Line styles (default is -):
Style Description Style Description
'-' Solid line '--' Dashed line
'-.' Dash-dot line ':' Dotted line
• Colours (default is b):
Letter Colour Letter Colour Letter Colour Letter Colour
'r' Red 'k' Black 'w' White 'y' Yellow
'g' Green 'm' Magenta 'c' Cyan 'b' Blue

120
• Let us create two different files:
– largefile.txt with many points forming a smooth curve,
– smallfile.txt with just a few, their ordinates (y-coordinates) created randomly

import numpy as np
import matplotlib.pyplot as plt

large = open ("largefile.txt","w")


small = open ("smallfile.txt","w")
# a few random numbers with integer x-coordinates
for xk in range(10):
yk=np.random.random()
small.write("%.20f %.20f"
⁀ % (xk,yk))
# a lot of numbers following a smooth curve (cosine)
for i in range(1000):
xk = i/200.
yk=np.cos(xk)
large.write("%.20f %.20f"
⁀ % (xk,yk))
large.close()
small.close()
contents of store.py
as you can see, numpy is not only useful for operations with arrays and matrices.
121
Exercises
• This file is incomplete and needs you to fill out the contents of function read from file:
import numpy as np
import matplotlib.pyplot as plt

# Function to read data from a file:


def read from file( filename ):
""" this function should return two lists:
- x containing the numbers of the first column of filename
- y containing the numbers in the second column of filename
"""
return x,y

smallx, smally = read from file( "smallfile.txt" )


largex, largey = read from file( "largefile.txt" )

# any other preliminary options


plt.plot(..., ...,"...")
plt.show()
contents of plot.py

The following pages show outputs of running plot.py, depending on what we write in the last
three lines.

122
For the smaller file. It is advised that you practice all possible options.

plt.plot(smallx, smally,"go-") plt.plot(smallx, smally,"ro")

plt.plot(smallx, smally,"^k:") plt.plot(smallx, smally,"ms--")

123
For the larger file. It is advised that you practice all possible options.

plt.plot(largex, largey,"k,") plt.plot(largex, largey,"k.")

plt.plot(largex, largey,'g-') plt.plot(largex, largey,'yo')

124
We can add other options, e.g.
– modify the aspect ratio and/or trim our image
– place text and labels

plt.plot(largex, largey,'r-') plt.plot(largex, largey,'r-')


plt.axes().set aspect('equal', 'datalim') plt.axes().set aspect('equal', 'box')
plt.xlabel('x - axis') plt.xlabel('x - axis')
plt.ylabel('y - axis') plt.ylabel('y - axis')
plt.title('Points of the cosine graph') plt.title('Points of the cosine graph')
plt.show() plt.show()

125
We can also typify plots as variables using figure and use functions involving them, e.g.

fig1 = plt.figure() # Make a new figure window fig1 = plt.figure()


plt.clf() # clear current figure plt.clf() # clear current figure
fig1.add subplot(3, 1,1) # 3 rows 1 column, plot 1 fig1.add subplot(2,1,1)
plt.plot(smallx, smally,'mo') plt.plot(smallx, smally,'mo')
fig1.add subplot(3, 1,2) # 3 rows 1 column, plot 2 fig1.add subplot(2,1,2)
plt.plot(smallx, smally,'go:') plt.plot(smallx, smally,'go:')
fig1.add subplot(3, 1,3) # 3 rows 1 column, plot 3 fig1.add subplot(1,2,2)
plt.plot(largex, largey,'r,') plt.plot(largex, largey,'r,')
plt.show() plt.show()

126
More grids of subplots with other methods:

fig, ax = plt.subplots(2, 2)
fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
ax[0, 0].plot(largex, largey, 'r')
ax1.plot(largex, largey)
ax[1, 0].plot(largex, largey, 'm')
ax1.set title('shared y axis')
ax[0, 1].plot(largex, largey, 'g')
ax2.plot(largex, largey,'go')
ax[1, 1].plot(largex, largey, 'k')
plt.show()
plt.show()

127
scatter works like plot ... 'o', i.e. without joining the dots:

x = np.arange(-1, 1.1, 0.3) x = np.arange(-1, 1.1, 0.3)


y = np.tan(x) y = np.tan(x)
plt.scatter(x, y,c='r') plt.scatter(x, y,c='w',marker='s',edgecolors='g')
plt.show() plt.show()

128
Important to note that joint plots are also immediate to program:

x = np.arange(-7, 7, 0.5)
y = np.cos(x**2)
X = np.arange(-7, 7, 0.01)
Y = np.cos(X**2)
xx = np.arange(-7, 7, 0.5)
yy = 3*np.cos(xx**2)
XX = np.arange(-7, 7, 0.01)
YY = 3*np.cos(X**2)
plt.axes().set aspect('equal', 'box')
plt.plot(X, Y,'r',x, y,'ko',XX,YY,'g-',xx, yy,'mo')
plt.show()

129
• We saw two weeks ago a method to approximate integrals: the composite trapezium rule, i.e.
divide interval [a, b] into n subintervals of equal length, then apply simple trapezium to each.
• The larger n, the smaller the length h of each subinterval, thus (theoretically) the closer Tn =
trapezium(f,a,b,n) is to the actual area.
b−a ∫b
(i) Write a program that plots h = n against a f − Tn .
(ii) We apply Richardson extrapolation to the well-known formula for the numerical error:
∫ b
b−a
Tn = f + Ah2 + higher terms of h, for some A, where h = .
a n
if we compute Tn and T2n, we can combine them to obtain better approximations:
∫b 2
} ∫ b
Tn = a f + Ah + . . . , 22T2n−Tn
⇒ R2 = = f + lowest term h4...
T2n = ab f + A( h2 )2 + . . . , 2 2 −1

a
This is called Romberg’s method and can be brought to higher orders (think how).
(iii) Plot Romberg output errors along with the plot in (i), check that indeed they provide better
approximations. Think of ways of plotting this that entail some clear visibility of all informa-
tion returned by your program.

130
11 In-class exercise
• A system of linear equations,

b1 = a1,1x1 + a1,2x2 + · · · + a1,mxm 

b2 = a2,1x1 + a2,2x2 + · · · + a2,mxm 

... ... ... (LS)



bn = an,1x1 + an,2x2 + · · · + an,mxm

can be written in matrix form:


     
a1,1 a1,2 . . . a1,m b1 x1
 a2,1 a2,2 . . . a2,m   b2   x2 
Ax = b where A = 
 ... ... ... ... 
, b=
 ...  ,
 x=
 ... 

an,1 an,2 . . . an,m bn xm
For instance,
x
      
2x − y + t = 1  2 −1 0 1 1
 y 
2 x + 9 t + 3 y = −5 ⇒ A =  2 3 0 9 , b =  −5  , x=
z 
−4 t + 8 x + y + z = 7  8 1 1 −4 7
t
A and b given, to solve (LS) is to find x satisfying all equations in the system.

131
• Possible cases if the matrix A is square (n = m):
– If det A ̸= 0, (LS) has a unique solution x.
– If det A = 0, (LS) can either have:
∗ infinitely many solutions x.
∗ or no solution at all.
• Examples:
– A system with only one solution:
9
  
x − y + 3z = 1  x= 23 1 −1 3
4
2x + 4 y + 2 z = 1 ⇒ y= 23
unsurprisingly because det  2 4 2  = −92 ̸= 0
7x + z = 3 6 7 0 1
z=

23

– A system with infinitely many solutions (in this case with two free variables, e.g. x and y):
det=0
            
1 2 3 x 1 x 0 1 0
 2 4 6  y = 2  ⇒  y  =  0  + x 0  + y 1 
1
4 8 12 z 4 z 3
− 13 − 23

– A system with no solutions:


    
1 2 3 x 1
 2 4 6  y  =  1 .
4 8 12 z 0

132
• Cramer’s rule provides a formula for the solution of Ax = b if n = m and det A ̸= 0:
|A1 | |A2 | |An |
x1 = , x2 = , ... xn = ,
|A| |A| |A|
where
– |A| is the determinant of the whole original matrix
– Aj is the determinant of the matrix obtained by replacing column j in A by vector b.
• For instance, for
    
3 1 2 x 1
 0 4 2  y  =  2 
1 0 1 z 4
the matrix has determinant 6 and Cramer’s rule implies:
1 1 2 3 1 2 3 1 1
2 4 2 0 2 2 0 4 2
4 0 1 11 1 4 1 10 1 0 4 23
x1 = =− , x2 = =− , x3 = = .
6 3 6 3 6 3
• Write a program that takes any system as input from an external file, does the adequate checks
and then solves it using Cramer. Make sure it keeps control of the time spent in the process.

133
12 Comments about the courseworkthis refers to 2019/2020
• Interpolation is the “completion” of known (and finite) data by assuming it fits a simple pattern.
• Some points are given in a table, for instance the relation between time t and the angle formed
by a satellite with the Ecliptic plane:
t 0. 0 .5 1. 1 .5 2
α 1.313 2.191 4.121 3.181 1.911
• We would like to know the angle α if t = 1.2, which does not appear in our experimental table:
α
(1, 4.121)

(1.5, 3.181)
(0.5, 2.191)
(2, 1.911)
(0, 1.313)

1.2 t

• We do not know whether α is indeed a function of t in real life; interpolation ignores this
question and proceeds to approximate α by a simple function p(t) = a0 + a1t + . . .
134
• This interpolating polynomial p(t) has degree at most one minus the number of points. Hence in
this case we expect a polynomial of degree four: p(t) = a0 + a1t + a2t2 + a3t3 + a4t4.
• The method proposed is using Newton’s divided differences:
0 1.313
❯❯❯❯❯
❯**
1.756
✐44 ❯❯❯❯❯
✐✐✐✐✐✐ ❯**
0.5 2.191❯❯❯❯ 2.104
✐✐

❯❯❯❯ ✐44 ❱❱❱❱❱


❯❯❯❯ ✐✐✐✐ ❱++
❯❯** ✐✐✐✐
✐✐✐44
3.86 ❯❯❯❯ -5.22933
✐✐✐✐ ❯❯❯❯ ❲❲❲❲
✐✐✐ ❯❯❯❯ ❤❤❤❤33 ❲++
✐✐✐✐ ❯** ❤❤❤❤❤
1 4.121 ❯❯❯❯ ✐44 -5.74 ❱❱❱❱ 4.308
❯❯❯❯ ✐✐✐✐✐✐ ❱❱❱❱
❱❱❱❱ ❤❤❤33
❯❯❯❯ ✐✐✐ ❱❱❱++ ❤❤❤❤
❯❯** ✐✐✐✐ ❤❤❤❤
❣❣33
-1.88 ❲❲❲❲❲ ❢❢❢33
3.38667
❣❣❣ ❣❣❣❣❣ ❲❲❲❲❲
❲❲++ ❢❢❢❢❢❢❢❢
1.5 3.181 -0.66
❣❣❣ ❢❢❢❢
❲❲❲❲❲ ❣❣33
❲❲❲❲❲
❲❲❲++ ❣❣❣❣❣❣❣❣
-2.54
❣❣❣
❣33
❣❣❣ ❣❣❣❣❣❣
2 1.911
❣❣❣

then the polynomial uses the marked elements in the upper row:
1.313 + (t − 0) 1.756 + (t − 0.5) 2.104 + (t − 1) -5.22933 + (t − 1.5) 4.308
{ [ ( )]}
p ( t) =
= 4.308t4 − 18.1533t3 + 21.795t2 − 5.14167t + 1.313.

135
• Expanded expression 4.308t4 − 18.1533t3 +21.795t2 − 5.14167t +1.313 is easy to complete and not practical
to us: it is the other expression 1.313 + (t − 0) {. . . } that minimises operations.
• The fact it minimises operations means you can evaluate p(t) numertically in thousands of values
of t in less than one second (and then plot the resulting graph of points):

and our desired approximation for α(1.2) (our initial question) is p(1.2) = 4.09191:
p(1.2) = 1.313 + (1.2 − 0) {1.756 + (1.2 − 0.5) [2.104 + (1.2 − 1) (−5.22933 + (1.2 − 1.5)4.308)]} = 4.09191

136
• Your program should be able to return (among other things) something like this:

• Check the sample Jupyter notebook provided in the assessment folder for sample things your
program could be providing.
137
• Your Jupyter notebook should not look remotely similar to the one provided. Write it in your
own style, with your own checks and peculiarities (any additions to it will be welcome as well).
• as a way to check your calculations, you can also solve the linear system attached to the in-
terpolating problem, using the method explained last week, e.g. for the given problem in the
first slide, we’re looking for a polynomial of degree one less than the number of points, p(t) =
a0 + a1t + a2t2 + a3t3 + a4t4, and fitting the polynomial to the table entails
 


 p(0) = 1.313 

 a0 = 1.313
 p(0.5) = 2.191  a0 + 0.5a1 + 0.25a2 + 0.125a3 + 0.0625a4 = 2.191

 

 
p(1) = 4.121 ⇒ a0 + a1 + a2 + a3 + a4 = 4.121
p(1.5) = 3.181 + 1.5a1 + 2.25a2 + 3.375a3 + 5.0625a4 = 3.181
 







 a0
 p(2) = 1.911
  a

+ 2a1 + 4a2 + 8a3 + 16a4 = 1.911
0
which you can solve for a0, . . . , a4 using Cramer’s rule and obtain a0 = 1.313, a1 =
−5.14167, a2 = 21.795, a3 = −18.1533, a4 = 4.308, unsurprisingly the same result ob-
tained earlier by expanding the polynomial.
We need to insist: solving the linear system is only a check (and a computationally ineffective
one) and will constitute 0 marks in itself. Your coursework must use divided differences.

138
• You are free to compute the set of divided differences in any way or form you deem fit (matrices,
recursive functions...). However,
– if you use an entire matrix to compute the differences, you are allocating memory for it and
that can be costly if the matrix is large;
– and if you compute divided differences recursively, you are computing most differences twice
(think why).
Hence if you avoid both matrices and recursiveness, you will get bonus marks.

Sergi Simon, December 6, 2019.


139
13 Classes and objects
• To be continued...

140
2 ACADEMIC SESSION: 2019–2020

INTRODUCTION TO
COMPUTATIONAL PHYSICS 6

First Coursework
U24200 –Academic Session 2019–2020 4

Instructions 2

a) This is worth 50% of your total mark for this unit.


b) You must undertake this assignment individually.
-6 -4 -2 2 4 6
c) Submission method: by Moodle through the available dropbox.
d) Submission deadline: January 13, 2019
-2

1 Introduction
The following result is fundamental to our purpose:
Interpolation consists in building a simple function f (here, a polynomial, f (x) = a0 + a1 x + · · · + ak xk )
whose graph curve passes through a given set of points Theorem (Existence and uniqueness of the interpolating polynomial). Let (x0 , y0 ) , . . . , (xn , yn ) be
n + 1 points in the plane such that xi are pairwise different (xi 6= xj if i 6= j). Then there exists a unique
p0 = (x0 , y0 ) , p1 = (x1 , y1 ) , ..., pm = (xm , ym ) , polynomial of degree at most n, pn (x) = a0 + a1 x + · · · + an xn , interpolating these points, i.e. such that
i.e. such that f (x0 ) = y0 , f (x1 ) = y1 , . . . , f (xm ) = ym . This can be seen, for instance in the case in which p (x0 ) = y1 , p (x1 ) = y2 , ,..., p (xn ) = yn . (1)
p0 = (−6.5, −1.2) , p1 = (−5.3, 0.05) , p2 = (−2.5, −1.2) , p3 = (1.23, 1.01) , Remarks.
p4 = (2.1, 2) , p5 = (4.7, 1.02) , p6 = (5.3, 2.5) , p7 = (7.1, 2.1) .
1. A well-known fact in Geometry, namely that any two points are traversed simultaneously by a unique
Let us first represent these points graphically: line, is a particular case of the above: a line is the graph of a degree-one polynomial y = ax + b, and
using the above two notation we would have n = 1 for two points (x0 , y0 ) , (x1 , y1 ).
p6
p4 p7 2. n is the maximum value (hence an upper bound) of the degree of the interpolating polynomial but the
actual degree could be less than n depending on the disposition of the points. For instance,
1.01 p3 p5
(x2 , y2 )
p1 −2.5
1.23 (x4 , y4 )
(x3 , y3 )
p2 (x)
p0 p2 −1.2 (x1 , y1 ) (x0 , y0 )

(x2 , y2 )
(x0 , y0 ) (x1 , y1 )
We wish to find a polynomial whose graph traverses each one of these points. After you have finished your
program, you will find that such a function can be approximated as Three points hence n = 2 but they are aligned, Five points (n = 4) but they are all in the same parabola,
thus interpolating polynomial is that line: thus interpolating polynomial is that parabola:
f (x) = −0.000175066x7 + 0.000289133x6 + 0.014541x5 − 0.0211649x4 − 0.34218x3 + 0.477095x2 + 2.24967x − 1.83489, p(x) = a + bx + 0 x2 p4 (x) = A + Bx + Cx2 + D 0 x3 + E 0 x4
and has the shape shown in the next page. We draw it along with the original points pi .
3. Interpolation (and a similar concept called extrapolation) will be useful whenever you are given a table
of experimental data and need to guess theoretical outputs for values not belonging to that table.

1
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 3 4 ACADEMIC SESSION: 2019–2020

There are many ways of computing the interpolating polynomial pn of n + 1 points (but remember: the 3 Newton’s divided differences
polynomial, due to its uniqueness, is still the same and depends only on the points chosen). Most notably:
Assume pn interpolates {(xk , yk ) : 0 ≤ k ≤ n} and we wish to find pn (x) for different values of x (for instance,
• solving the linear system defined by the interpolating conditions pn (3) , pn (3.4) , pn (6.7)). The method described in §2 would make this difficult unless we perform the final
expansion (3); otherwise we would need to compute new Lagrange polynomials for each value of x. If,
• method of Lagrange polynomials;
however, we could fix some coefficients A0 , A1 , . . . , An depending only on the fixed abscissae x0 , . . . , xn and
• Newton’s method of divided differences; not on the mobile point x and could fit them in an expression

• Aitken’s method, Neville’s method, etc. pn (x) = A0 + A1 (x − x0 ) + A2 (x − x0 ) (x − x1 ) + A3 (x − x0 ) (x − x1 ) (x − x2 ) + · · · +


We will see the third method. The first two are mostly of theoretical utility but we will give you an example + An (x − x0 ) (x − x1 ) · · · (x − xn−1 ) (4)
of application of the second one to further illustrate the uniqueness of the polynomial for each table.
this would make it easier for us to work out pn (x) in any x using a tool that reduces the number of
operations: generalised Horner’s method: start with Un = An and perform the following:
2 Lagrange polynomials
Uk = Uk (x − xk−1 ) and Uk−1 = Uk + Ak−1 , k = n, . . . , 1. (5)
Let x0 , . . . , xn be n+1 pairwise different abscissae. For every k = 0, 1, . . . , n, the k th Lagrange polynomial
linked to the abscissae x0 , . . . , xn is For instance, for n = 3 we would have
n U0
Y x − xi (x − x0 ) · · · (x − xk−1 ) (x − xk+1 ) · · · (x − xn )
Lk = = U1
i=0, i6=k
xk − xi (xk − x0 ) · · · (xk − xk−1 ) (xk − xk+1 ) · · · (xk − xn )
U2
 

  

  U3
Then, the interpolating polynomial for the table {(x0 , y0 ) , . . . , (xn , yn )} is

 

p3 (x) =A0 + (x − x0 ) A1 + (x − x1 ) A2 + (x − x2 ) A3  .
 
pn (x) = y0 L0 (x) + y1 L1 (x) + · · · + yn Ln (x) .

 


 

Example. Assume we want to find the interpolating polynomial for the following table:
Newton’s method of divided differences provides an expression of the form (4). Given a table
x 1 2 4 8 15 {(xk , yk ) : k = 0, . . . , n}, x0 , . . . , xm pairwise different, we recursively define the divided differences as:
(2)
f (x) −0.5 0.4 0.9 1.5 1.9
y [xk ] = yk = yk ,
The Lagrange polynomials are: yk+1 − yk
y [xk , xk+1 ] = yk,k+1 = ,
xk+1 − xk
(x − 2) (x − 4) (x − 8) (x − 15) (x − 2) (x − 4) (x − 8) (x − 15)
L0 (x) = = , yk+1,k+2 − yk,k+1
(1 − 2) (1 − 4) (1 − 8) (1 − 15) 294 y [xk , xk+1 , xk+2 ] = yk,k+1,k+2 = ,
(x − 1) (x − 4) (x − 8) (x − 15) (x − 1) (x − 4) (x − 8) (x − 15)
xk+2 − xk
L1 (x) = =− , ..
(2 − 1) (2 − 4) (2 − 8) (2 − 15) 156 .
(x − 1) (x − 2) (x − 8) (x − 15) (x − 1) (x − 2) (x − 8) (x − 15) yk+1,...,k+m − yk,...,k+m−1
L2 (x) = = , y [xk , . . . , xk+m ] = yk,k+1,...,k+m = .
(4 − 1) (4 − 2) (4 − 8) (4 − 15) 264 xk+m − xk
(x − 1) (x − 2) (x − 4) (x − 15) (x − 1) (x − 2) (x − 4) (x − 15)
L3 (x) = =− , Both notations (boxed or double-boxed) are equally correct. These provide the A0 , . . . , An in (4) for pn :
(8 − 1) (8 − 2) (8 − 4) (8 − 15) 1176
(x − 1) (x − 2) (x − 4) (x − 8) (x − 1) (x − 2) (x − 4) (x − 8) Theorem. The interpolating polynomial for a table {(xk , yk ) : 0 ≤ k ≤ n} is
L4 (x) = =
(15 − 1) (15 − 2) (15 − 4) (15 − 8) 14014

and the interpolating polynomial is pn (x) = y [x0 ] + y [x0 , x1 ] (x − x0 ) + y [x0 , x1 , x2 ] (x − x0 ) (x − x1 )


+ y [x0 , x1 , x2 , x3 ] (x − x0 ) (x − x1 ) (x − x2 ) + · · · +
(x − 2) (x − 4) (x − 8) (x − 15) (x − 1) (x − 4) (x − 8) (x − 15)
p4 (x) = (−0.5) − (0.4) + (. . .) + y [x0 , x1 , . . . , xn ] (x − x0 ) · · · (x − xn−1 ) (6)
294 156
(x − 1) (x − 2) (x − 4) (x − 15) (x − 1) (x − 2) (x − 4) (x − 8)
− (1.5) + (1.9)
1176 14014
= −2.18962 + 2.18947x − 0.55636x2 + 0.0585058x3 − 0.00199562x4 , (3)

or p4 (x) = − 153427 306833 6683 2 8199 3 839 4


70070 + 140140 x − 12012 x + 140140 x − 420420 x if you had been working with exact rational
amounts (i.e. replacing −0.5 by − 21 , 0.4 by 52 , etc in the original table). In general, you will not have the
easy option to convert to rational form and you will need to work with float as done in (3).
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 5 6 ACADEMIC SESSION: 2019–2020

Example. We return to the table in (2). The divided difference scheme if we use pen and paper is 4 Coursework exercises
k xk yk yk−1,k yk−2,k−1,k yk−3,k−2,k−1,k yk−4,...,k 1. These will be the functions used to compute pn :
0 1 − 12
4
10 − (− 21 ) 9 (i) Write up a function Newton_differences with the following arguments:
2−1 = 10
1 9
4 13
1 2 10
4 − 10
4−1 = − 60 • an array of abscissae x0 , . . . , xn that have been previously checked to be pairwise different;
9 4
1 1
10 − 10
4−2 = 4 35 • an array of ordinates y0 , . . . , yn comprising the other half of the table we wish to interpolate.
9
3 1
1 839
(7)
2 4 10
20 − 4
8−2 = − 60 − 420420
3 9
2 − 10
= 3 19 and returning the array of divided differences (y0 , y0,1 . . . , y0,...,n ).
8−4 20 30030
2 3
3 13
3 8 2
35 − 20
15−4 = − 1540 (ii) Write up a function Horner with the following arguments:
19 3
2
10 − 2
15−8 = 35
4 15 19 • an array of abscissae x0 , . . . , xn that have been previously checked to be pairwise different;
10
• an array of terms A0 , . . . , An ,
and we keep the elements in the upper diagonal row. The polynomial is
• and a variable floating-point number x,
p4 (x) = y0 + y0,1 (x − x0 ) + y0,1,2 (x − x0 ) (x − x1 ) + y0,1,2,3 (x − x0 ) (x − x1 ) (x − x2 )
+y0,1,2,3,4 (x − x0 ) (x − x1 ) (x − x2 ) (x − x3 ) and returning the output A0 + (x − x0 ) (A1 + (x − x1 ) (A2 + . . .)) in the manner described in (5).
1 9 13 1 839
= − + (x − 1) − (x − 1) (x − 2) + (x − 1) (x − 2) (x − 4) − (x − 1) (x − 2) (x − 4) (x − 8) (iii) Combine Exercises (i) and (ii) above to interpolate a given set of points and compute pn (x) for any
2 10 60 35 420420
153427 306833 6683 2 8199 3 839 4 given x. Think of possible ways to minimise the number of operations used.
= − + x− x + x − x (8)
70070 140140 12012 140140 420420
2. And these constitute an example of how to apply Exercise 1:
Unsurprisingly, the same polynomial we found for this given set of points using Lagrange’s method. If we
want to compute p4 (x) for any x, if we use (8) we need to carry out 14 +/−, 10 ∗. However, using Horner, (i) Create a text file table.txt and fill it with pairs of numbers distributed in two columns:
   
1 9 13 1 839 
p4 (x) = − + (x−1) +(x − 2) − +(x−4) − (x−8) x0 y0 
2 10 60 35 420420 
x1 y1 make sure x0, x1,... are all different (10)
which only requires eight sums or subtractions and four products. .. .. 
. .

Again, in general (and more specifically in this coursework) you will not have the luxury of working
with pen and paper, thus you will need to keep numbers in float decimal point form:
(ii) Write a function read_from_file with two arguments: a file name (in string form) and a tolerance tol.
xk yk yk−1,k yk−2,k−1,k yk−3,k−2,k−1,k yk−4,...,k
The function will read two-column data such as (10) externally from file name and will check that there
1 -0.5
0.4−(−0.5) are no abscissae x0, x1, . . . (in any order) differing from each other by less than tol in absolute value.
2−1 = 0.9
0.25−0.9
2 0.4 4−1 ≃ -0.216667 (iii) Write a function write_to_files one of whose arguments must be the number of points of the inter-
0.9−0.4
4−2 = 0.25 0.0285714 (9) polating polynomial you wish to plot. It will write these points, again in the same disposition as (10),
0.15−0.25
4 0.9 8−2 ≃ -0.0166667 -0.00199562 in a new file called function_to_plot.txt. It will also store Newton’s divided differences in another
1.5−0.9
8−4 = 0.15 0.000632701 file named divided_differences.txt.
0.0571429−0.15
8 1.5 15−4 ≃ -0.00844155
1.9−1.5
15−8 ≃ 0.0571429
15 1.9 All that is needed for you to upload to Moodle is:

The boxed terms in (9) are the ones we keep for the polynomial. Note that regardless of whether you use • One Python file UPXXXXXX.py containing your student number.
finite precision (9) or the exact rational values (7), you need to compute the entire table to retrieve that
upper row of boxed items. Now for every x, the value of p4 (x) at x is • One Jupyter library showcasing applications of (and any necessary comments about) your Python file.

p4 (x) = −0.5 + (x − 1) · (0.9 + (x − 2) · (−0.216667 + (x − 4) · (0.0285714 + (x − 8) · (−0.00199562)))) • One file table.txt containing a sample table to interpolate.

which, if expanded, has the same form p4 = −2.1896249 + 2.1894750x + . . . as in (3). Instead of expanding • One file function_to_plot.txt with a larger (> 1000) set of points interpolating those in table.txt.
it, however, if we wanted to compute it for different values of x we would use Horner (5): • One file divided_differences.txt containing the divided differences for the data in table.txt.
U4 = −0.00199562, U4 = U4 (x − 8), U3 = U4 + 0.0285714, U3 = U3 (x − 4),
• A plot of the data in function_to_plot.txt (you can skip this if your Jupyter file contains plots).
U2 = U3 − 0.216667, U2 = U2 (x − 2), U1 = U2 + 0.9, U1 = U1 (x − 1), U0 = U1 − 0.5
and the value of p4 (x) for that particular x would be U0 .
2 ACADEMIC SESSION: 2019–2020

INTRODUCTION TO
COMPUTATIONAL PHYSICS 6

Second Assessment for Teaching Block 1


U24200 –Academic Session 2019–2020 4

Instructions 2

a) The proportion of marks for this coursework


b) You must undertake this assignment individually.
-6 -4 -2 2 4 6
c) Submission method: by Moodle through the available dropbox.
d) Submission deadline: August 7, 2020
-2

1 Reminder about interpolation (already seen in Coursework 1)


The following result is fundamental to our purpose:
Interpolation consists in building a simple function f (here, a polynomial, f (x) = a0 + a1 x + · · · + ak xk )
whose graph curve passes through a given set of points Theorem (Existence and uniqueness of the interpolating polynomial). Let (x0 , y0 ) , . . . , (xn , yn ) be
n + 1 points in the plane such that xi are pairwise different (xi 6= xj if i 6= j). Then there exists a unique
p0 = (x0 , y0 ) , p1 = (x1 , y1 ) , ..., pm = (xm , ym ) , polynomial of degree at most n, pn (x) = a0 + a1 x + · · · + an xn , interpolating these points, i.e. such that
i.e. such that f (x0 ) = y0 , f (x1 ) = y1 , . . . , f (xm ) = ym . This can be seen, for instance in the case in which p (x0 ) = y1 , p (x1 ) = y2 , ,..., p (xn ) = yn . (1)
p0 = (−6.5, −1.2) , p1 = (−5.3, 0.05) , p2 = (−2.5, −1.2) , p3 = (1.23, 1.01) , Remarks.
p4 = (2.1, 2) , p5 = (4.7, 1.02) , p6 = (5.3, 2.5) , p7 = (7.1, 2.1) .
1. A well-known fact in Geometry, namely that any two points are traversed simultaneously by a unique
Let us first represent these points graphically: line, is a particular case of the above: a line is the graph of a degree-one polynomial y = ax + b, and
using the above two notation we would have n = 1 for two points (x0 , y0 ) , (x1 , y1 ).
p6
p4 p7 2. n is the maximum value (hence an upper bound) of the degree of the interpolating polynomial but the
actual degree could be less than n depending on the disposition of the points. For instance,
1.01 p3 p5
(x2 , y2 )
p1 −2.5
(x4 , y4 )
1.23 (x3 , y3 )
p2 (x)
p0 p2 −1.2 (x1 , y1 ) (x0 , y0 )

(x2 , y2 )
(x0 , y0 ) (x1 , y1 )
We wish to find a polynomial whose graph traverses each one of these points. After you have finished your
program, you will find that such a function can be approximated as Three points hence n = 2 but they are aligned, Five points (n = 4) but they are all in the same parabola,
thus interpolating polynomial is that line: thus interpolating polynomial is that parabola:
f (x) = −0.000175066x7 + 0.000289133x6 + 0.014541x5 − 0.0211649x4 − 0.34218x3 + 0.477095x2 + 2.24967x − 1.83489, p(x) = a + bx + 0 x2 p4 (x) = A + Bx + Cx2 + D 0 x3 + E 0 x4
and has the shape shown in the next page. We draw it along with the original points pi .
3. Interpolation (and a similar concept called extrapolation) will be useful whenever you are given a table
of experimental data and need to guess theoretical outputs for values not belonging to that table.

1
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 3 4 ACADEMIC SESSION: 2019–2020

There are many ways of computing the interpolating polynomial pn of n + 1 points (but remember: the 3 Coursework exercises
polynomial, due to its uniqueness, is still the same and depends only on the points chosen). Most notably:
1. These will be the functions used to compute pn :
• solving the linear system defined by the interpolating conditions

• method of Lagrange polynomials; (i) Implement a function Cramer to solve linear systems with an invertible matrix. If you check through
the Moodle material for TB1 you will find this, but make sure you write it in your own style (don’t
• Newton’s method of divided differences; just copy it) and test it on separate systems before adapting it to your coursework.
• Aitken’s method, Neville’s method, etc.
(ii) Write up a function VandermondeSolution with the following arguments:
In Coursework 1 we saw the third method. We will now focus on the first method for this Coursework.
• an array of abscissae x = (x0 , . . . , xn ) that have been previously checked to be pairwise different;
2 Solving a linear system • an array of ordinates y = (y0 , . . . , yn ) comprising the other half of the table we wish to interpolate.
The polynomial pn (x) = an xn + an−1 xn−1 + · · · + a1 x + a0 must verify (1), i.e.
and returning matrix V and the solution a of the system V a = y, where
an xn0 + an−1 xn−1
0 + · · · + a1 x0 + a0 = y0 , (2)
an xn1 + an−1 xn−1
1 + · · · + a1 x1 + a0 = y1 , (3) • V is the Vandermonde matrix for x0 , . . . , xn . Feel free to choose either version: (6) or (7).
..
. (4) • a = (an , . . . , a0 ) or a = (a0 , . . . , an ) is the array of coefficients of the desired an xn + · · · + a0 .
an xnn + an−1 xn−1
n + · · · + a1 xn + a0 = yn , (5)
(iii) Write up a function Horner with the following arguments:
these are n + 1 linear equations with n + 1 unknowns a0 , . . . , an . It can be proved that the matrix of this
linear system, called a Vandermonde matrix, • an array of terms a0 , . . . , an ,
xn0 x0n−1
 
... x0 1 • and a variable floating-point number x,

 xn1 x1n−1 ... x1 1 

xn2 x2n−1 ... x2 1
 
V =  (6) and returning the output a0 + x (a1 + x (a2 + . . .)) where you minimise the number of operations used.
.. .. .. .. ..
 

 . . . . .

 You can adapt the generalised Horner’s method seen in Coursework 1, only that in this simpler case
xnn xnn−1 ... xn 1 you will multiply each new block by x instead of by the successive x − xn−1 , x − xn−2 , etc.

has a determinant 6= 0, thus the linear system defined by (2), (3), . . . , (5) has a unique solution (an , an−1 , . . . , a0 ). (i), (ii), (iii) above are enough to interpolate a given set of points and compute pn (x) for any given x.
These are the coefficients of the polynomial we are looking for.
Sometimes you will also see the matrix written the other way, 2. And these constitute an example of how to apply Exercise 1:
xn−1 xn0
 
1 x0 ... 0
 1 x1 ... xn−1 xn1  (i) Create a text file table.txt and fill it with pairs of numbers distributed in two columns:
 1 
1 x2 ... xn−1 xn2 
 
 2 (7)
.. .. .. .. .. 
  
x0 y0
. . . . . 
 
 
n−1 n x1 y1 make sure x0, x1,... are all different (8)
1 xn ... xn xn .. .. 
. .

in which case the solution will be written in the opposite order: (a0 , a1 , . . . , an−1 , an ). This is just
as correct as if you used (6).
(ii) Write a function read_from_file with two arguments: a file name (in string form) and a tolerance tol.
The function will read two-column data such as (8) externally from file name and will check that there
are no abscissae x0, x1, . . . (in any order) differing from each other by less than tol in absolute value.

(iii) Write a function write_to_files one of whose arguments must be the number of points of the interpo-
lating polynomial you wish to plot. It will write these points, again in the same disposition as (8), in a
new file called function_to_plot.txt. It will also store the Vandermonde matrix, ordinates y0 , . . . , yn
and coefficients of your polynomial in another file named matrix_and_vectors.txt.
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 5

All that is needed for you to upload to Moodle is:

• One Python file UPXXXXXX.py containing your student number.

• One Jupyter library showcasing applications of (and any necessary comments about) your Python file.

• One file table.txt containing a sample table xi | yi to interpolate.

• One file matrix_and_vectors.txt containing the Vandermonde matrix V , the ordinates y and
the solution a = (an , . . . , a0 ) to the system V a = y, i.e. the coefficients of pn .

• One file function_to_plot.txt with a larger (> 1000) set of points interpolating those in table.txt.

• A plot of the data in function_to_plot.txt (you can skip this if your Jupyter file contains plots).

Mark scheme: if your program

• does none of the following: run without errors, produce a plot or a numerical output: 0-50 marks

• fails to do at least one of the things described above: 30-60 marks

• runs without errors and produces one plot, but lacks what is said in the items below: 50-70 marks

• same as item above and the interpolating process is correct for arbitrary degrees: 60-90 marks

• same as item above and a good Jupyter notebook is present: 80-100 marks

Mark ranges overlap because the global layout of your program and your ability to detect glitches will also
count.
2 ACADEMIC SESSION: 2020–2021

INTRODUCTION TO Two situations can arise, depending on the function f (x) you are working with:
COMPUTATIONAL PHYSICS • sometimes finding or describing zeros of a function symbolically will be an easy task, e.g.

First Coursework – f (x) = x − 1 has only one root α = 1,


√ √
− 5−1 5−1
U24200 –Academic Session 2020–2021 – f (x) = x2 + x − 1 has two roots α = 2 , β= 2 that are easy to find exactly
using the quadratic formula,
(2k+1)π
– f (x) = cos(x) has infinitely many roots but we know them: (k any integer),
Instructions 2
– and f (x) = ex has no zeros at all, because it is strictly positive on all values of x;
a) This is worth 50% of your total mark for this unit.
by symbolically we mean roots we can represent exactly because we do not require decimal
b) You must undertake this assignment individually.
approximations to write them down √ (even if we may need approximations to operate with
c) Submission method: by Moodle through the available dropbox. them in real-life scenarios, using 5 ≃ 2.23607 . . . and π ≃ 3.14159 . . . );
d) Submission deadline: January 22, 2020, 4PM
• most oftentimes, however, we may determine the amount of roots – but finding one, let
alone all, symbolically will be either difficult or impossible. For instance, a thorough study
of the roots of e−x = x (i.e. the zeros of function f (x) = e−x − x) reveals that there is
only one such zero α and it lies in the interval (0.5, 0.6). But we cannot describe α in
1 Introduction closed form, unlike the above examples; we cannot write it as an integer, a multiple of
a known constant (e.g. π), a combination of square roots or simple functions of known
Let f (x) be a continuous function of a single variable. It can be: values, etc. The only thing we can do is approximate α with a fixed amount of correct
• a simple function, e.g. a polynomial f (x) = an xn +· · ·+a1 x+a0 (for instance f (x) = x−1, significant digits, e.g. α ≃ 0.5671432904097838 with 16 correct decimal digits and α ≃
f (x) = 3x2 + 4x − 5, etc) 0.56714329040978387299996866221035 with 32 correct decimal digits.

• or a more complicated function such as f (x) = cos x, f (x) = ex , ...


y = x2 + x − 1
Having one real input x and one real output y = f (x) for every input, this function can be y =x−1
represented by a two-dimensional graph plotting abscissae x against ordinates y: α
y 1 β


−b± b2 −4ac
y = f (x) f (x) = 0 equates to x = 1 Formula 2a applied to Function f (x) = e−x − x has only one
ax2 + bx + c, a = 1, b = 1, c = −1 zero α but we cannot find it exactly

Numerical solution of equations consists in approximating one or more roots of a given


equation f (x) = 0 using certain root-finding algorithms. Some remarks are in order here:
(a, f (a))
• these algorithms will work regardless of whether or not we can find the roots symbolically,
e.g. for f (x) = x − 1 we know the exact solution, x = 1, but we can also apply a
root-finding algorithm if we want to; if implemented correctly, it will approximate
α1 α2 a α3 α4
x x ≃ 1. large amount of 0’s other digits or x ≃ 0. large amount of 9’s other digits

and the better the approximation, the more 0’s or 9’s after the decimal point.

• As said above, most functions will not afford us the luxury of a symbolic solution, and
We have highlighted four values α1 , α2 , α3 , α4 in the above example. They are values of x such numerical root-finding algorithms to approximate it will be the only tools available.
that f (x) = 0, i.e. the graph of f intersects the x-axis. We usually call such values zeros (or
• Before applying the root-finding algorithm, we need to know the precision, i.e. the number
roots) of the function f , or roots (or simply solutions) of the equation f (x) = 0.
of correct digits that we desire, e.g. 16 in the first approximation of α for e−x = x above.

1
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 3 4 ACADEMIC SESSION: 2020–2021

Most root-finding algorithms are iterative methods: they entail the creation of a sequence 2 The secant method
of numbers {xk }k≥0 such, that every new iteration (i.e. element of the sequence) xk can be
obtained as a fixed function of a fixed number of previous iterations: This is a two-point iterative method, i.e. j = 2 meaning every iteration is a function of the
previous two. Assume we have chosen two initial conditions x0 , x1 , chosen close to the root α
xk = G (xk−1 , xk−2 , . . . , xk−j ) G and j being fixed and depending on the method. (1) that we are after:

This requires, as you will realise yourselves, a set of j initial conditions x0 , . . . , xj−1 .
Assume you want to fix a precision; more specifically, assume you wish to approximate a f (x1 )
root α with p correct decimal digits. You can do so by fixing a certain tolerance ε = 21 10−p , every new iteration is the intersection of the
which is what you will use as a criterion to stop your iteration process. Let us represent a few x-axis with the secant line (thus the name)
containing the points on the graph built from
steps of this method:
the previous two iterations
xj = G (xj−1 , xj−2 , . . . , x0 ) (1st actual step of the method, using only initial conditions)
xj+1 = G (xj , xj−1 , . . . , x1 ) (2nd step, using previous one xj and some initial conditions)
x2 x3
xj+2 = G (xj+1 , xj , xj−1 , . . . , x2 ) (3rd step)
.. .. .. x0 x4 x1
. . .
f (x3 )
xm = G (xm−1 , xm , . . . , xm−j ) (mth step)
.. .. ..
. . . x2 = G (x1 , x0 ) ,
x3 = G (x2 , x1 ) ,
The process will stop at a step n where two consecutive iterations differ by less than ε: f (x2 ) x4 = G (x3 , x2 ) ,
..
1 .
|xn − xn−1 | < 10−p , i.e. xn and xn−1 share their first p decimal digits. (2)
2
Obviously, the challenge here is finding a function G, depending on the original function f , such f (x0 )
that the sequence generated above converges to the root we are looking for: limn→∞ xn = α.
Once G is established (and we will not explain the mathematical reasoning behind this now), x4 is already fairly close to (but still distinguishable from) the root. It is reasonable to
condition (2) ensures, in most cases, that the last iteration xn in our method will assume that x5 , x6 , . . . will be even closer, and proceeding in this manner we will obtain
an xk sharing the desired number of decimal digits with α. Simple geometry shows that
share p correct decimal digits with α as well.
the intersection of line (xn−1 , f (xn−1 )) (xn−2 , f (xn−2 )) with the x-axis is G (xn−1 , xn−2 ) =
There are many examples of iterative methods, i.e. choices of G: xn−1 − f (xn−1 ) f (xxn−1
n−1 −xn−2
)−f (xn−2 ) , meaning the secant method has the following form:

• the secant method, which may or may not converge depending on initial conditions; xn−1 − xn−2
xn = xn−1 − f (xn−1 ) , n ≥ 2, having chosen initial conditions x0 , x1 (3)
f (xn−1 ) − f (xn−2 )
• Newton’s method, which may or may not converge but if it does, is usually faster;

• the bisection method, which converges more often but does so extremely slowly;
Example. Assume we want to find the largest root of f (x) = x2 − 1. We know how to find it
• other methods, such as Steffensen’s method, fixed-point iteration, Halley’s method, etc. symbolically (it is = 1), hence we do not need a numerical method – but that does not mean
we cannot apply one. First let us express G in terms of f :
We will see the first three methods. 
xn−1 − xn−2 x2 − 1 (xn−1 − xn−2 ) xn−2 xn−1 + 1
G (xn−1 , xn−2 ) = xn−1 −f (xn−1 ) = xn−1 − n−1 2 =
f (xn−1 ) − f (xn−2 ) xn−1 − x2n−2 xn−2 + xn−1

Thus our iteration method will be the following. We are using pen and calculator instead of
Python here, so we can afford simplifying G:
xn−2 xn−1 + 1
xn = , n ≥ 2.
xn−2 + xn−1
It being a two-point method, we need two initial conditions. This is up to the method user.
Bear in mind that convergence (as well as convergence speed) of the method may
depend on an adequate choice of these conditions. Choosing unwisely can result in
convergence to the wrong root (−1), slower convergence to 1, or no convergence at all.
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 5 6 ACADEMIC SESSION: 2020–2021

For example, choose x0 = 0, x1 = 2. Let us fix precision ε = 21 10−16 and start our process: 3 Newton’s Method
x0 = 0 (initial condition chosen by us) Also called Newton-Raphson method, it is a one-point algorithm, i.e. j = 1: every iteration
x1 = 2 (initial condition chosen by us) only needs the previous one: xk = G (xk−1 ), k ≥ 1. Thus only one initial condition is needed:
x0 x1 + 1 0·2+1 1 f (x1 )
x2 = G (x1 , x0 ) = = =
x0 + x1 0+2 2
x1 x2 + 1 2 · 21 + 1 4 every new iteration xk is the intersection of
x3 = G (x2 , x1 ) = = = = 0.8
x1 + x2 2 + 12 5 the x-axis with the tangent (instead of the
x2 x3 + 1 1
· 4
+1 14 secant) to the graph through (xk−1 , f (xk−1 ))
2 5
x4 = G (x3 , x2 ) = = 1 = ≃ 1.0769230769230769231
x2 + x3 2 + 45 13
4
x3 x4 + 1 · 14 + 1 121
x5 = G (x4 , x3 ) = = 4 13 14 =
5
≃ 0.99180327868852459016
x3 + x4 5 + 13
122
3280
x6 = G (x5 , x4 ) = ≃ 0.99969521487351417251 x0 x2 = G(x1 ) x1 = G(x0 )
3281
797162
x7 = G (x6 , x5 ) = ≃ 1.0000012544517355967
797161
5230176601
x8 = G (x7 , x6 ) = ≃ 0.99999999980880186730
5230176602
8338590849833284
x9 = G (x8 , x7 ) = ≃ 0.9999999999999998801
8338590849833285
87224605504560089535585254
x10 = G (x9 , x8 ) = ≃ 1.00000000000000000000000001146.
87224605504560089535585253 f (x0 )
1454660594681285404315232913246121223340241
x11 = G (x10 , x9 ) = ≃ 0. 42 digits = 9 3125544
1454660594681285404315232913246121223340242
f (xk )
Basic Geometry and Calculus yield an explicit expression: G (xk ) = xk − f ′ (xk ) , where f ′ is the
We have |x11 − x10 | < 12 10−16 , thus x11 is our last iteration and the process can stop. Our
derivative of f . Thus our method will be
approximation for the root is x11 = 0.999 · · · , and if we round it up to 16 digits we get 1.
Needless to say, choosing other initial conditions will change the numbers (and potentially, f (xn−1 )
xn = xn−1 − , n ≥ 1, having chosen initial condition x0 (4)
the number of iterations needed) in the above process. Exercise: try this yourselves, e.g. f ′ (xn−1 )
x0 = 1/2 and x1 = 3/2. This method usually requires less iterations than secant to approximate the root
with the same precision. Same as with secant, convergence to the desired root is
Example. Let us try f (x) = e−x − x shown in page 2. Unlike the previous example, we now not guaranteed here. Try to think of situations leading to lack of convergence.
do not have a simple representation of root α. We know (after looking at its graph) that it lies
between 0.5 and 0.6, thus these two can be the initial conditions. Our iteration function will be Example. Return to f (x) = e−x − x. f ′ (x) = −e−x − 1, thus our iteration function will be
−xn−1
f (x) e−x − x x+1
xn−1 − xn−2 (e − xn−1 ) (xn−1 − xn−2 ) G (x) = x − =x− = x ,
G (xn−1 , xn−2 ) = xn−1 − f (xn−1 ) = xn−1 − f ′ (x) −e−x − 1 e +1
f (xn−1 ) − f (xn−2 ) xn−2 − e−xn−2 + e−xn−1 − xn−1
exn−2 xn−2 − exn−1 xn−1 Choose initial condition x0 = 1 and the first iterations of (4) become:
=
exn−1 (exn−2 (xn−2 − xn−1 ) − 1) + exn−2 f (x0 ) 2
x1 = G (x0 ) = x0 − = ≃ 0.53788284273999024150
f ′ (x0 ) 1+e
thus our process will be: f (x1 )
x2 = G (x1 ) = x1 − ≃ 0.5669869914054132388
f ′ (x1 )
e0.5 0.5 − e0.6 0.6
x2 = G (x1 , x0 ) = G (0.6, 0.5) = ≃ 0.5675445848373013947 f (x2 )
e0.6 (e0.5 (0.5− 0.6) − 1) + e0.5 x3 = G (x2 ) = x2 − ≃ 0.5671432859891229440
f ′ (x2 )
ex1 x1 − ex2 x2
x3 = G (x2 , x1 ) = x2 x1 ≃ 0.5671409166735748153, f (x3 )
e (e (x1 − x2 ) − 1) + ex1 x4 = G (x3 ) = x3 − ≃ 0.567143290409783869
f ′ (x3 )
ex2 x2 − ex3 x3 f (x4 )
x4 = G (x3 , x2 ) = x3 x2 ≃ 0.5671432905821386311 x5 = G (x4 ) = x4 − ≃ 0.567143290409783873;
e (e (x2 − x3 ) − 1) + ex2 f ′ (x4 )
.. .. ..
. . . iteration x5 shares at least 16 digits with x4 (|x5 − x4 | < 21 10−16 ), thus the mathematical
discussion we have omitted here implies that it also shares those digits with the actual root.
until x7 ≃ 0.567143290409783873 which satisfies |x7 − x6 | < 12 10−16 , thus we have our approxi- Thus we can stop here and 0.5671432904097838 is our approximation. Exercise: try an initial
mation 0.567143290409783873 of the root with 16 correct decimal digits. condition x0 closer to the root, say 0.55, and observe the algorithm converge even faster.
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 7 8 ACADEMIC SESSION: 2020–2021

4 The bisection method 5 The Actual Coursework


This is the most intuitive method and has the advantage of converging regardless of our choice Write a Python program to approximate the roots of any function. It must contain, at least,
of initial conditions if the zero entails a change in sign, as well as not requiring derivatives or the following functions.
complicated manipulation of our function f (x) – but is also the slowest method available.
The idea behind bisection is simple: if a root α lies between a and b (i.e. inside the interval 1. A routine initial_plot plotting the graph of any f (x), then giving the user the option to
[a, b]) and we divide, or hbisect, ithis interval into twohsubintervals of equal length, then α belongs change the range interval and create new plots, or stop plotting altogether. You will use this
i function to zoom in or out until you have spotted a root of f , thus giving you good candidates
to either the left half a, a+b 2 , or the right half
a+b
2 , b . Once we know which of the two for initial condition(s) close to it before you implement the methods in 2 below.
subintervals it belongs to, we apply the exact same argument to that subinterval: dividing it
into two, and discerning which half contains α. Each subinterval will be 1/2 the size of the 2. Functions Secant, Newton, Bisection for approximating roots numerically. Each of these
previous one, thus we will be progressively cornering α in smaller intervals until we have an functions should have at least the following parameters passed to them as inputs:
interval whose length is smaller than the precision tolerance 21 10−p .
Let us describe the iteration. We need two initial conditions a0 , b0 (extremes of the initial • the function f (x) itself and, in one of the three methods, its derivative f ′ (x);
interval containing α) and then proceed as follows for n = 0, 1, . . . , until |an − bn | < 12 10−p .
• the initial condition(s) x0,... required by the given method;
What is done in iteration n:
• the tolerance tol determining the precision with which you wish to approximate the root.
an +bn
• At this stage we have [an , bn ] containing α. Compute the midpoint cn = 2 .
Your functions must write initial conditions and all iterations in an external file.
Option 1: If f (an ) f (cn ) < 0 that means α ∈ [an , cn ]. Start iteration n + 1, replacing [an , bn ] Think of ways in which to fill out this external file with at least two columns, so that:
by the smaller interval [an , cn ], and repeat the process.
• one of the columns corresponds to the index k = 0, . . . determining the iteration;
Option 2: If f (bn ) f (cn ) < 0 the right half-interval [cn , bn ] contains α. Start iteration n + 1
by defining [an+1 , bn+1 ] to be this interval [cn , bn ], and repeat the process. • and another one for the iteration xk itself.

Make sure you write this in a way that makes it readable from another Python routine.
f (x)
3. Function read_from_file reading an external file and returning the column corresponding to
the iterations of whatever method (Secant, Newton or Bisection) filled the file originally. If the
a0 c0 b0 file is not arrayed in proper columns, read_from_file should detect this and notify the user.

4. A routine final_plot doing the exact same thing as final_plot, and in addition having
f (x) slightly larger, properly labelled dots for some of the iterations, along with captions containing
all the relevant information.
a1 c1 b1
Let XXXXXX be your student number. All that is needed for you to upload to Moodle is:

f (x) • One Python file UPXXXXXX_MAIN.py containing the main body of your program.

c2 • One Python file UPXXXXXX_METHODS.py containing your methods and, perhaps, other routines.
a1 b2 • Optional UPXXXXXX_FUNCTIONS.py if you wish to define your different f (x) , f ′ (x) in a separate file.

• One Jupyter notebook showcasing applications of, and comments about, your Python project.
Needless to say, iteration n = 3 in the above example would arise from taking a3 = c2 and
b 3 = b2 . • A few sample .txt files containing iterations of all three methods applied to functions.

• A few initial plots, showing how you located and zoomed into roots of at least one function
(you can skip this if your Jupyter file contains plots)

• A final plot obtained from final_plot. Again, you can skip this if your Jupyter file contains plots.
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 9

Mark ranges: if your program

• does none of these: run without errors, produce a plot or a numerical output: 0-45 marks

• fails to do at least one of the things described above: 30-60 marks

• runs without errors and produces one plot, but lacks what is said below: 45-70 marks

• same as item above and the process is correct for arbitrary f (x): 60-90 marks

• same as item above and a good Jupyter notebook is present: 80-100 marks

Mark ranges overlap because the global layout of your program and your ability to detect glitches
will also count.
2 ACADEMIC SESSION: 2020–2021

INTRODUCTION TO Two situations can arise, depending on the function f (x) you are working with:
COMPUTATIONAL PHYSICS • sometimes finding or describing zeros of a function symbolically will be an easy task, e.g.

Referral/Deferral – Python Section – f (x) = x − 1 has only one root α = 1,


√ √
− 5−1 5−1
U24200 –Academic Session 2020–2021 – f (x) = x2 + x − 1 has two roots α = 2 , β= 2 that are easy to find exactly
using the quadratic formula,
(2k+1)π
– f (x) = cos(x) has infinitely many roots but we know them: (k any integer),
Instructions 2
– and f (x) = ex has no zeros at all, because it is strictly positive on all values of x;
a) You must undertake this assignment individually.
by symbolically we mean roots we can represent exactly because we do not require decimal
b) Submission method: by Moodle through the available dropbox.
approximations to write them down √ (even if we may need approximations to operate with
them in real-life scenarios, using 5 ≃ 2.23607 . . . and π ≃ 3.14159 . . . );

• most oftentimes, however, we may determine the amount of roots – but finding one, let
1 Introduction alone all, symbolically will be either difficult or impossible. For instance, a thorough study
of the roots of e−x = x (i.e. the zeros of function f (x) = e−x − x) reveals that there is
Let f (x) be a continuous function of a single variable. It can be: only one such zero α and it lies in the interval (0.5, 0.6). But we cannot describe α in
closed form, unlike the above examples; we cannot write it as an integer, a multiple of
• a simple function, e.g. a polynomial f (x) = an xn +· · ·+a1 x+a0 (for instance f (x) = x−1, a known constant (e.g. π), a combination of square roots or simple functions of known
f (x) = 3x2 + 4x − 5, etc) values, etc. The only thing we can do is approximate α with a fixed amount of correct
• or a more complicated function such as f (x) = cos x, f (x) = ex , ... significant digits, e.g. α ≃ 0.5671432904097838 with 16 correct decimal digits and α ≃
0.56714329040978387299996866221035 with 32 correct decimal digits.
Having one real input x and one real output y = f (x) for every input, this function can be
represented by a two-dimensional graph plotting abscissae x against ordinates y:
y = x2 + x − 1
y y =x−1
α

1 β
y = f (x) α


−b± b2 −4ac
f (x) = 0 equates to x = 1 Formula 2a applied to Function f (x) = e−x − x has only one
ax2 + bx + c, a = 1, b = 1, c = −1 zero α but we cannot find it exactly

(a, f (a))
Numerical solution of equations consists in approximating one or more roots of a given
equation f (x) = 0 using certain root-finding algorithms. Some remarks are in order here:

α1 α2 a α3 α4 • these algorithms will work regardless of whether or not we can find the roots symbolically,
x e.g. for f (x) = x − 1 we know the exact solution, x = 1, but we can also apply a
root-finding algorithm if we want to; if implemented correctly, it will approximate

x ≃ 1. large amount of 0’s other digits or x ≃ 0. large amount of 9’s other digits

We have highlighted four values α1 , α2 , α3 , α4 in the above example. They are values of x such and the better the approximation, the more 0’s or 9’s after the decimal point.
that f (x) = 0, i.e. the graph of f intersects the x-axis. We usually call such values zeros (or
• As said above, most functions will not afford us the luxury of a symbolic solution, and
roots) of the function f , or roots (or simply solutions) of the equation f (x) = 0.
numerical root-finding algorithms to approximate it will be the only tools available.

• Before applying the root-finding algorithm, we need to know the precision, i.e. the number
of correct digits that we desire, e.g. 16 in the first approximation of α for e−x = x above.

1
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 3 4 ACADEMIC SESSION: 2020–2021

Most root-finding algorithms are iterative methods: they entail the creation of a sequence 3 The Actual Coursework
of numbers {xk }k≥0 such, that every new iteration (i.e. element of the sequence) xk can be
obtained as a fixed function of a fixed number of previous iterations: Write a Python program to approximate the roots of any function. It must contain, at least,
the following functions.
xk = G (xk−1 , xk−2 , . . . , xk−j ) G and j being fixed and depending on the method. (1)
1. A routine initial_plot plotting the graph of any f (x), then giving the user the option to
This requires, as you will realise yourselves, a set of j initial conditions x0 , . . . , xj−1 . zoom and create new plots, or stop plotting altogether. This is identical to the TB1 coursework.
Assume you want to fix a precision; more specifically, assume you wish to approximate a
root α with k correct decimal digits. You can do so by fixing a certain tolerance ε = 21 10−k , 2. A function Householder for approximating roots numerically using Householder methods.
which is what you will use as a criterion to stop your iteration process. The process (1) will This function should have at least the following parameters passed to them as inputs:
stop at a step n where two consecutive iterations differ by less than ε:
• the value of p described above; your program should be operational for at least
1 one value > 1 of p; you already know how to program Newton (p = 1) so that
|xn − xn−1 | < 10−k , i.e. xn and xn−1 share their first k decimal digits. (2)
2 would entail 0 marks here.
Obviously, the challenge here is finding a function G, depending on the original function f , such
• the function f (x) itself and its derivatives f ′ (x) , f ′′ (x) , ..., upper bound depending on
that the sequence generated above converges to the root we are looking for: limn→∞ xn = α.
which values of p you choose; for instance, if you only choose p = 3 then your program
Once G is established (and we will not explain the mathematical reasoning behind this now),
should be able to use derivatives f ′ (x) , f ′′ (x) , f ′′′ (x).
condition (2) ensures, in most cases, that the last iteration xn in our method will
share p correct decimal digits with α as well. • the initial condition x0 required by the given method;
There are many examples of iterative methods, i.e. choices of G: • the tolerance tol determining the precision with which you wish to approximate the root.
• secant, Newton and bisection, all of which you saw during the first semester;
Your function must write initial conditions and all iterations in external files (for
• other methods, such as Steffensen’s method, fixed-point iteration, Halley’s method, etc. different functions and different values of p), arrayed in two columns, so that:

We will see a family of methods generalizing Newton. • one of the columns corresponds to the index k = 0, . . . determining the iteration;

• and another one for the iteration xk itself.


2 Householder’s methods
2
Make sure you write this in a way that makes it readable from another Python routine.
Denote (p) to be the pth order derivative, e.g. f (0) = f, f (1) = f ′ , f (2) = f ′′ = ddxf2 , etc.
Householder methods are one-point algorithms, i.e. j = 1: every iteration only needs the 3. Function read_from_file reading an external file and returning the column corresponding
previous one: xk = G (xk−1 ), k ≥ 1. Thus only one initial condition is needed. The general to the iterations with which the method (Householder) filled the file originally. Identical to the
form of the pth Householder method is: same function described in the TB1 coursework.

F (p−1) (xn ) 1 4. A routine final_plot doing the exact same thing as final_plot, and in addition having
xn+1 = xn + p , where F (x) := . (3) slightly larger, properly labelled dots for some of the iterations, along with captions containing
F (p) (xn ) f (x)
all the relevant information. Identical to the TB1 coursework.
For example, if p = 1 Householder becomes Newton’s method which you already know:
1 f ′ (x) Let XXXXXX be your student number. All that is needed for you to upload to Moodle is:
F (p−1) (x) = F (0) (x) = F (x) = , F (p) (x) = F ′ (x) = (1/f )′ (x) = − ,
f (x) f (x)2
• One Python file UPXXXXXX_MAIN.py containing the main body of your program.
using the quotient or the chain rule for derivation. Thus (3) becomes in this case
• One Python file UPXXXXXX_METHODS.py containing your methods and, perhaps, other routines.
1
f (xn ) f (xn )
xn+1 = xn + 1 · f ′ (xn )
= xn − , • Optional UPXXXXXX_FUNCTIONS.py if you wish to define functions and derivatives in a separate file.
− f (xn )2 f ′ (xn )
• A few sample .txt files containing iterations of some methods (i.e. values of p) applied to functions.
which should be familiar to you from TB1 when you were programming Newton’s method.
The order of convergence of the method, if it does converge, is p+1 (which is why Newton • A few initial plots, showing how you located and zoomed into roots of at least one function.
was generally considered to be quadratic, i.e. of order 2, hence the number of correct decimal
places roughly doubled after every iteration). • A final plot obtained from final_plot.
Thus, for higher values of p, you obtain faster methods. The only drawback, of course, is • Optional: a Jupyter notebook if it makes any of the above easier.
that you need to make the additional effort to compute higher derivatives; you will realize this
yourselves when you find the expression for Householder for values p > 1.
INTRODUCTION TO COMPUTATIONAL PHYSICS – U24200 5

Mark ranges: if your program

• does none of these: run without errors, produce a plot or a numerical output: 0-45 marks

• fails to do at least one of the things described above: 30-60 marks

• runs without errors and produces one plot, but lacks what is said below: 45-70 marks

• same as item above and the process is correct for arbitrary f (x): 60-100 marks

• two Householder methods instead of one (e.g. p = 2, 3, p = 2, 4, etc): 10 bonus marks

Mark ranges overlap because the global layout of your program and your ability to detect glitches
will also count.

You might also like