1. Introduction to Python: preliminaries
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python 0 May 13, 2019 1 / 4
3. Comments
◮ No TAs: please help each other! (Except the exercises that are
submitted via email)
◮ Feedback (good or bad) welcome:
patrick.farrell@maths.ox.ac.uk
P. E. Farrell (Oxford) Python 0 May 13, 2019 3 / 4
4. Preview: final challenge!
MMSC: for this to count for your MSc, you have to write a final report.
Your task:
◮ Implement a finite element discretisation of a PDE.
◮ Take a discretisation and configuration from the literature, or invent
your own.
◮ Bonus points for: nonlinearity, coupling, mathematical interest, . . .
◮ Include an MMS verification.
◮ Submit working code and a report (∼ 10 pages) discussing the PDE
and its implementation.
◮ Inspiration: your research projects; the PDE coffee table book.
◮ Deadline: unsure, will ask Kathryn.
P. E. Farrell (Oxford) Python 0 May 13, 2019 4 / 4
5. Introduction to Python: native datatypes
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python I May 13, 2019 1 / 21
6. What is Python?
◮ Very high-level language
◮ Powerful complex data structures
◮ Supports imperative, functional, object-oriented programming
◮ Dynamic, extremely rapid development
◮ Free (very useful for parallel)
◮ Main scripting language used in large-scale scientific computing
◮ Beautiful, and fun . . .
P. E. Farrell (Oxford) Python I May 13, 2019 2 / 21
8. Running Python
Option 1: run interactively
$ ipython
In [1]: print("Hello, world")
Hello, world
(mainly for development)
Option 2: put code in a file and run
$ cat hello.py
print("Hello, world")
$ python hello.py
Hello, world
(mainly for production runs)
P. E. Farrell (Oxford) Python I May 13, 2019 4 / 21
9. Interactive calculator
>>> 1 + 1
2
>>> a = 1
>>> a
1
>>> type(a)
<type 'int'>
>>> 5 % 3
2
>>> b = 17.03 + 19.85
>>> type(b)
<type 'float'>
>>> int(b)
36
>>> c = 2 + 1.5j
>>> type(c)
<type 'complex'>
>>> c.imag
1.5
>>> c.conjugate()
(2-1.5j)
>>> s = "Hello, world!"
>>> type(s)
<type 'str'>
>>> len(s)
13
>>> s.split()
['Hello,', 'world!']
P. E. Farrell (Oxford) Python I May 13, 2019 5 / 21
10. Integer division
Be aware of integer division in Python 2 (à la C/C++):
>>> 1/2
0
>>> # ?!?!
>>> 1.0/2
0.5
>>> 1.0/2.0
0.5
Use // to unambiguously specify integer division:
>>> 1.0//2.0
0.0
This is fixed in Python 3:
>>> 1/2
0.5
P. E. Farrell (Oxford) Python I May 13, 2019 6 / 21
11. Booleans and conditionals
Booleans behave as you expect:
>>> not False
True
>>> True and False
False
>>> True or False
True
Booleans can be used for conditionals:
>>> x = 5
>>> if 4 < x < 6:
... print("x in [4, 6]")
...
x in [4, 6]
P. E. Farrell (Oxford) Python I May 13, 2019 7 / 21
12. Strings and slicing
You can make literal strings in several ways:
>>> a = "This wouldn't work with '"
>>> b = '"Arrest that man!", he cried.'
>>> c = """
... A large block
... of text
... """
Strings can be indexed and subscripted:
>>> b[0]
'"'
>>> b[1:7]
'Arrest'
>>> b[-6:]
'cried.'
P. E. Farrell (Oxford) Python I May 13, 2019 8 / 21
13. More strings
The Python str class is very powerful.
>>> dir(b)
... # too many methods to list
>>> b.swapcase()
'"aRREST THAT MAN!", HE CRIED.'
>>> b.replace("man", "donkey")
'"Arrest that donkey!", he cried.'
>>> print(b + ''' "Why? He's done nothing wrong."''')
"Arrest that man!", he cried. "Why? He's done nothing wrong."
>>> b.split('!')
['"Arrest that man', '", he cried.']
P. E. Farrell (Oxford) Python I May 13, 2019 9 / 21
14. String interpolation
Strings can be interpolated:
>>> address = "Woodstock Road"
>>> postcode = "OX2 6GG"
>>> print("Our address is %s, %s" % (address, postcode))
Our address is Woodstock Road, OX2 6GG
>>> from math import pi
>>> print("Pi: %1.5f" % pi)
Pi: 3.14159
P. E. Farrell (Oxford) Python I May 13, 2019 10 / 21
15. Compound types: lists
Python has very powerful, fast native data structures. Here we’ll introduce
four: lists, tuples, dictionaries and sets.
A list contains, well, a list of objects of any kind:
>>> l = [1, 1, 2, 3, 5, 8]
>>> l += ['can', 'be', 'different']
>>> l
[1, 1, 2, 3, 5, 8, 'can', 'be', 'different']
>>> l[0]
1
>>> l[-1]
'different'
>>> range(4)
[0, 1, 2, 3]
>>> len(range(4))
4
P. E. Farrell (Oxford) Python I May 13, 2019 11 / 21
16. Compound types: lists
A list has several methods that modify it in-place:
>>> l.sort()
>>> l.reverse()
>>> l
['different', 'can', 'be', 8, 5, 3, 2, 1, 1]
>>> l[-1] = 10
Lists can be sliced and joined, too:
>>> l = ['c', 'l', 'o', 'u', 'd', 's']
>>> l[1:5]
['l', 'o', 'u', 'd']
>>> '-'.join(l)
'c-l-o-u-d-s'
>>> ''.join(l)
'clouds'
P. E. Farrell (Oxford) Python I May 13, 2019 12 / 21
17. Compound types: lists
The Python keyword for ∈ is in:
>>> l = range(5, 10)
>>> l
[5, 6, 7, 8, 9]
>>> 7 in l
True
>>> 10 in l
False
P. E. Farrell (Oxford) Python I May 13, 2019 13 / 21
18. List iteration
Lists can be iterated over:
>>> res = 0
>>> for i in l:
... res = res + i
...
>>> res
35
>>> sum(l)
35
P. E. Farrell (Oxford) Python I May 13, 2019 14 / 21
19. List comprehension
In mathematics, we write statements like:
V =
x2
: x ∈ S
In Python, we can write analogous code: list comprehensions.
l = range(4)
[x**2 for x in l]
[0, 1, 4, 9]
[x**2 for x in l if x % 2 == 0]
[0, 4]
P. E. Farrell (Oxford) Python I May 13, 2019 15 / 21
20. List comprehension
List comprehensions can feature multiple loops:
[j**k for j in range(5) for k in range(2)]
[1, 0, 1, 1, 1, 2, 1, 3, 1, 4]
P. E. Farrell (Oxford) Python I May 13, 2019 16 / 21
21. Dictionaries
A dictionary is an unordered table that maps keys to values. The keys
have to be hashable
numbers = {Carol: 2714, Hannah: 3852, Marie: 7268}
numbers[Carol]
'2714'
Carol in numbers
True
numbers.keys()
['Hannah', 'Carol', 'Marie']
numbers.values()
['3852', '2714', '7268']
for name in numbers:
... number = numbers[name]
... print(%s - %s % (name, number))
...
Hannah - 3852
Carol - 2714
Marie - 7268
P. E. Farrell (Oxford) Python I May 13, 2019 17 / 21
22. Dictionaries
The dictionary can natively map different types to different types.
mydict = {'a': 1, True: hello}
P. E. Farrell (Oxford) Python I May 13, 2019 18 / 21
23. Tuples
Lists are mutable: you can change them in-place. A tuple is an immutable
list. You can create and unpack tuples like so:
t = (0, 1)
t[0] = 2
Traceback (most recent call last):
File stdin, line 1, in module
TypeError: 'tuple' object does not support item assignment
(a, b) = t
a
0
Because they can’t be changed after creation, tuples are used:
◮ to pass arguments into functions
◮ to return values from functions
◮ as keys in dictionaries.
P. E. Farrell (Oxford) Python I May 13, 2019 19 / 21
24. Sets
Our final in-built container type is the set.
{1, 2, 3, 3, 2} == {1, 2, 3}
True
{1, 2, 3} == {1, 3, 2}
True
{1, 3, 5}.union({2, 4})
{1, 2, 3, 4, 5}
P. E. Farrell (Oxford) Python I May 13, 2019 20 / 21
25. Python 01 Challenge!
Using a single line, build a list of the numbers from 1 to N = 100 that are
not prime.
Using a single line, then build a list of the numbers from 1 to N that are
prime.
(This is called the sieve of Eratosthenes.)
Bonus question: for what is Eratosthenes most famous?
P. E. Farrell (Oxford) Python I May 13, 2019 21 / 21
26. Introduction to Python: functions
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python II May 13, 2019 1 / 15
27. Functions
Functions are a central idea in mathematics.
def subtract(x1, x2):
return x1 - x2
Note that functions, like all other blocks, must be indented.
By default, arguments are assigned to parameters by order:
subtract(5.0, 4.3) # returns 0.7
We can also specify arguments by keyword:
subtract(x2=4.3, x1=5.0) # returns 0.7
P. E. Farrell (Oxford) Python II May 13, 2019 2 / 15
28. Input to functions
Default values for parameters can be specified:
def remainder(number, divisor=1):
return number % divisor
Valid calls:
remainder(20)
remainder(20, divisor=7)
remainder(number=20, divisor=7)
remainder(divisor=7, number=20)
We can also pass arguments in a dictionary:
d = {'number': 20, 'divisor': 7}
remainder(**d)
P. E. Farrell (Oxford) Python II May 13, 2019 3 / 15
29. Input to functions
Functions can take a variable number of arguments, too:
def printer(*args, **kwargs):
print(args: %s % (args,))
print(kwargs: %s % kwargs)
printer('hello', 'world', argument=4, another='black')
This prints
args: ('hello', 'world')
kwargs: {'argument': 4, 'another': 'black'}
P. E. Farrell (Oxford) Python II May 13, 2019 4 / 15
30. Output from functions
A function always returns one single object.
By default, functions return None:
from math import pi
def circle_area(radius):
area = pi * radius**2 # oops! forgot to return
area = circle_area(1.0) # returns None
If you want to return more than one value, construct a tuple:
from math import sqrt, atan2
def complex_to_polar(z):
r = sqrt(z.real**2 + z.imag**2)
phi = atan2(z.imag, z.real)
return (r, phi) # the tuple is one object
P. E. Farrell (Oxford) Python II May 13, 2019 5 / 15
31. Functions modifying inputs
Functions can modify mutable arguments (can be dangerous!).
def try_to_modify(a, b, c):
a = 23
b.append('gotcha')
c = [99, 'problems']
a = 54
b = ['hello']
c = ['world']
try_to_modify(a, b, c)
a == 54 # True
b == ['hello'] # False
c == ['world'] # True
Be careful!
P. E. Farrell (Oxford) Python II May 13, 2019 6 / 15
32. Documenting functions
Functions carry their own documentation:
from math import pi
def circle_stats(radius):
This function computes the perimeter and area of a circle.
Usage:
(perimeter, area) = circle_stats(radius)
return (2*pi*radius, pi*radius**2)
Python functions are objects like any other, and we can interrogate them
for their documentation:
print circle_stats.__doc__
help(circle_stats)
P. E. Farrell (Oxford) Python II May 13, 2019 7 / 15
33. Functions are objects
Functions are objects and can be passed in to other functions.
def deprecated(fun):
def wrapped_fun(*args, **kwargs):
print(Warning: function %s is deprecated. % fun)
return fun(*args, **kwargs)
return wrapped_fun
def myfun(x):
return x + 1
myfun = deprecated(myfun)
myfun(2)
P. E. Farrell (Oxford) Python II May 13, 2019 8 / 15
34. Functions are objects
This usage of functions modifying functions has a shorthand: decorators.
def deprecated(fun):
def wrapped_fun(*args, **kwargs):
print(Warning: function %s is deprecated. % fun)
return fun(*args, **kwargs)
return wrapped_fun
@deprecated # equivalent to myfun = deprecated(myfun)
def myfun(x):
return x + 1
myfun(2)
P. E. Farrell (Oxford) Python II May 13, 2019 9 / 15
35. Recursive functions
Functions can call themselves:
def chebyshev(n, x):
This function computes the Chebyshev polynomial of
degree n at a point x with the recursion formula
T_n(x) = 2*x*T_{n-1}(x) - T_{n-2}(x)
print(chebyshev(%s, %s) called % (n, x))
if n == 0:
return 1.0
elif n == 1:
return x
else:
return 2.0 * x * chebyshev(n - 1, x) - chebyshev(n - 2, x)
Recursive definitions are mathematically elegant but can be
computationally wasteful.
P. E. Farrell (Oxford) Python II May 13, 2019 10 / 15
36. Functions are objects
Let’s make it much faster with a decorator!
def memoise(fun):
cache = {}
def memoised_fun(*args, **kwargs):
kitems = tuple(kwargs.items())
key = (args, kitems)
if key not in cache:
cache[key] = fun(*args, **kwargs)
return cache[key]
return memoised_fun
@memoise
def chebyshev(n, x):
...
Introspection and dynamism are very very powerful.
P. E. Farrell (Oxford) Python II May 13, 2019 11 / 15
37. Useful decorators
Two extremely useful decorators: line_profiler and memory_profiler.
from line_profiler import profile
def sleep():
seconds = random.randint(0, 5); time.sleep(seconds)
@profile
def test():
sleep()
sleep()
sleep()
Line # Hits Time Per Hit % Time Line Contents
==============================================================
8 @profile
9 def test():
10 1 3999416 3999416.0 36.4 sleep()
11 1 4999982 4999982.0 45.5 sleep()
12 1 1999990 1999990.0 18.2 sleep()
P. E. Farrell (Oxford) Python II May 13, 2019 12 / 15
38. Useful decorators
from memory_profiler import profile
@profile
def my_func():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
del b
return a
Line # Mem usage Increment Line Contents
==============================================
3 @profile
4 5.97 MB 0.00 MB def my_func():
5 13.61 MB 7.64 MB a = [1] * (10 ** 6)
6 166.20 MB 152.59 MB b = [2] * (2 * 10 ** 7)
7 13.61 MB -152.59 MB del b
8 13.61 MB 0.00 MB return a
P. E. Farrell (Oxford) Python II May 13, 2019 13 / 15
39. Anonymous functions
Functions can be defined anonymously (like matlab @):
def square(x):
return x*x
# Equivalently
square = lambda x: x*x
The lambda refers to λ-calculus.
P. E. Farrell (Oxford) Python II May 13, 2019 14 / 15
40. Python 02 Challenge!
(a) Write a function ab3(f, u0, T, dt) that solves an ODE
du
dt
= f(u), u(0) = u0, u : [0, T] 7→ R
using the 3rd-order Adams-Bashforth discretisation with timestep dt. Return
u(T).
(b) Implement the bisection rootfinding algorithm in a function bisect(f, (a,
b), tol). Test your implementation on f(x) = x3
− 6x2
+ 11x − 6, on the
intervals [0.5, 1.5] and [1.5, 2.5].
(c) The greatest common divisor of two integers can be computed using Euclid’s
algorithm:
gcd(a, b) =
(
a, if b = 0
gcd(b, a mod b), otherwise
Implement a function gcd(a, b).
P. E. Farrell (Oxford) Python II May 13, 2019 15 / 15
41. Introduction to Python: modules, namespaces and
plotting
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python III May 13, 2019 1 / 16
42. Modules
Python code is organised into modules. Python comes with many powerful
modules by default.
We can import a module with the import statement:
import math
print(math.sin(math.pi)) # 1.22e-16
The math module defines its own namespace: a collection of Python
objects (e.g. variables, functions, classes). Namespaces allow clean
separation of code with the same name:
import math
import scipy
print(math.sin(math.pi)) # only works on single numbers
print(scipy.sin([math.pi, 2*math.pi])) # works on arrays
P. E. Farrell (Oxford) Python III May 13, 2019 2 / 16
43. Namespaces
We can also import objects from another namespace into our current
namespace. For example:
from math import sin, pi
print(sin(pi))
or even
from math import *
print(sin(pi))
P. E. Farrell (Oxford) Python III May 13, 2019 3 / 16
44. More importing
We can import modules with different names to their installed name. For
example:
import math as m
print(m.sin(m.pi))
We can also import elements of a namespace with a different name:
from math import sin as s, pi as p
print(s(p))
P. E. Farrell (Oxford) Python III May 13, 2019 4 / 16
45. Namespaces
We can investigate all of the objects a namespace exports with dir:
import math
print(dir(math))
For non-built-in modules, you can also find out where its code lives:
import scipy
print(scipy.__file__)
print(scipy.__version__)
P. E. Farrell (Oxford) Python III May 13, 2019 5 / 16
46. Making modules
You can make your own modules by putting your code in a file. Use your
favourite text editor to create a file fib.py:
def fibonacci(u0, u1, N):
l = [u0, u1]
for i in range(N-1):
l.append(l[-1] + l[-2])
return l
In another file, main.py:
import fib
print(fib.fibonacci(1, 1, 10))
P. E. Farrell (Oxford) Python III May 13, 2019 6 / 16
47. Making modules
Each namespace has a __name__ attribute:
import math
math.__name__
'math'
If this module is the main script being executed as python myscript.py,
its name is set to __main__. This can be used to protect test code:
def fibonacci(u0, u1, N):
l = [u0, u1]
for i in range(N-1):
l.append(l[-1] + l[-2])
return l
if __name__ == __main__:
# won't get executed on 'import fib'
f10 = fibonacci(1, 1, 10)
assert f10[-1] == 89
P. E. Farrell (Oxford) Python III May 13, 2019 7 / 16
48. Plotting
In the rest of this lecture we’ll look at one important module, matplotlib.
from scipy import *
from matplotlib.pyplot import *
x = linspace(-2*pi, 2*pi, 200)
plot(x, sin(x))
samples = x[::4]
plot(samples, sin(samples), 'bo', markersize=10)
title(sin(x))
grid()
savefig(sin.pdf)
P. E. Farrell (Oxford) Python III May 13, 2019 8 / 16
49. Plotting
8 6 4 2 0 2 4 6 8
1.0
0.5
0.0
0.5
1.0
sin(x)
P. E. Farrell (Oxford) Python III May 13, 2019 9 / 16
50. Plotting
from scipy import *
from matplotlib.pyplot import *
x = range(5)
y = [1, 2, 1, 3, 5]
p2 = polyfit(x, y, 2)
p4 = polyfit(x, y, 4)
xx = linspace(-1, 5, 200)
plot(xx, polyval(p2, xx), label=degree 2)
plot(xx, polyval(p4, xx), label=degree 4)
plot(x, y, 'b*', markersize=10)
axis([-1, 5, 0,6])
legend(loc='upper left')
title('Polynomial fitting')
P. E. Farrell (Oxford) Python III May 13, 2019 10 / 16
51. Plotting
1 0 1 2 3 4 5
0
1
2
3
4
5
6 Polynomial fitting
degree 2
degree 4
P. E. Farrell (Oxford) Python III May 13, 2019 11 / 16
52. Plotting
from scipy import *
from matplotlib.pyplot import *
def mandelbrot(h, w, maxit=20):
'''Returns image of Mandelbrot fractal of size (h, w)'''
x = linspace(-2, 0.8, w)
y = linspace(-1.4, 1.4, h)
X, Y= meshgrid(x, y)
c = X + Y*1j
z = c
divtime = maxit + zeros(z.shape, dtype=int)
for iteration in xrange(maxit):
z = z**2 + c
diverge = z*conj(z) 2**2
div_now = diverge (divtime == maxit)
divtime[div_now] = iteration
z[diverge] = 2
return divtime
P. E. Farrell (Oxford) Python III May 13, 2019 12 / 16
56. Python 03 Challenge!
Put your code for solving an ODE into a module. Adapt it to plot the
trajectory, if an optional argument plot is set to True.
In a different module, use it to solve the ODEs u′ = ±u over [0, 5] for
u(0) = 1. Plot them both at the same time.
P. E. Farrell (Oxford) Python III May 13, 2019 16 / 16
57. Introduction to Python: iteration
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python IV May 13, 2019 1 / 17
58. Iteration
Iteration is what makes computers useful. Your course is largely about
designing iterations that converge to some desired quantity, after all.
As we’ve seen, the simplest iteration in Python is looping over elements of
a list:
for s in ['a', 'b', 'c']:
print(s), # prints 'a b c'
It’s also possible to iterate on a Boolean conditional:
while f(c) tol:
# compute a better guess for the root c
return c
P. E. Farrell (Oxford) Python IV May 13, 2019 2 / 17
59. break, continue and pass
Sometimes we need to exit loops early. For example, interacting with users
could look like:
while True:
s = input(Enter a string (quit to quit): )
if s == quit:
break
print(String length: %s % len(s))
P. E. Farrell (Oxford) Python IV May 13, 2019 3 / 17
60. break, continue and pass
Sometimes we want to skip one particular iteration of a loop, but not exit
from the entire iteration:
for i in range(100):
if isprime(i):
continue
# Do something with composite number i
P. E. Farrell (Oxford) Python IV May 13, 2019 4 / 17
61. break, continue and pass
Sometimes we want to do nothing, but Python expects some valid code.
This is what the pass statement is for:
while True:
pass # will loop forever doing nothing
P. E. Farrell (Oxford) Python IV May 13, 2019 5 / 17
62. Iterable objects
for loops aren’t just for iterating over lists; we can iterate over any
iterable object:
for val in tuple:
...
for key in dict:
...
P. E. Farrell (Oxford) Python IV May 13, 2019 6 / 17
63. Iterable objects
Another example of an iterable object: files.
mydata = open(mydata.txt, r)
for line in mydata:
print(line.split())
We’ll see more about I/O later.
P. E. Farrell (Oxford) Python IV May 13, 2019 7 / 17
64. Generators
In particular, we can iterate over an object that creates each value of the
iteration on-the-fly. Such an object is called a generator. In Python 2, we
had
for i in range(1000000000):
# stores the entire list 0 to
# 1000000000 in memory all at ance
for i in xrange(1000000000):
# yields each item one at a time,
# O(1) memory usage
In Python 3 most things are generators under the hood.
P. E. Farrell (Oxford) Python IV May 13, 2019 8 / 17
65. Generators
You can define your own generators with the yield keyword.
def fibonacci(u0, u1):
yield u0
yield u1
while True:
(u0, u1) = (u1, u0 + u1)
yield u1
for fib in fibonacci(1, 1):
# do something with Fibonacci numbers,
# will loop forever unless you break
This pattern is extremely useful for numerical computing: it separates
generating computed values, using them, and terminating.
P. E. Farrell (Oxford) Python IV May 13, 2019 9 / 17
66. Generator comprehensions
Another way to define a generator: like a list comprehension, but with
(...) instead of [..].
sum((x*x for x in xrange(10)))
In this special case (where the generator is the only argument), we can
drop the brackets:
sum(x*x for x in xrange(10))
P. E. Farrell (Oxford) Python IV May 13, 2019 10 / 17
67. Generators
Here are some useful tools.
enumerate takes in an iterator and produces a new iterator that yields
pairs (index, element):
A = ['a', 'b', 'c']
for (index, x) in enumerate(A):
print(iteration, x)
# result: (0, 'a') (1, 'b') (2, 'c')
P. E. Farrell (Oxford) Python IV May 13, 2019 11 / 17
68. Generators
reversed takes in a finite iterable and goes through this list backwards.
A = ['a', 'b', 'c']
for x in reversed(A):
print(x)
# result: c b a
P. E. Farrell (Oxford) Python IV May 13, 2019 12 / 17
69. Generators
sorted takes in a finite iterable and returns an iterator that yields the
elements in order.
A = ['c', 'b', 'a']
for x in sorted(A):
print(x)
# result: a b c
P. E. Farrell (Oxford) Python IV May 13, 2019 13 / 17
70. Generators
next takes in an iterator and returns the next value of the sequence.
from itertools import count
counter = count()
i = next(counter)
print(i) # 0
i = next(counter)
print(i) # 1
P. E. Farrell (Oxford) Python IV May 13, 2019 14 / 17
71. Generators
itertools.islice truncates an iterator.
from itertools import count, islice
for x in islice(count(), 10):
# same effect as xrange(10)
P. E. Farrell (Oxford) Python IV May 13, 2019 15 / 17
72. Python 04 Challenge!
Aitken’s ∆2–method accelerates the convergence of sequences by
transforming a slowly-converging sequence into a faster-converging one
with the same limit. The formula is
ŝi = si −
(si+1 − si)2
si+2 − 2si+1 + si
.
Write a generator that takes in a generator for a sequence and yields the
Aitken acceleration of that sequence. As example input, use the sequence
sN =
N
X
n=0
(−1)n
2n + 1
,
which converges to π/4 (slowly!).
Hint: value = next(sequence).
P. E. Farrell (Oxford) Python IV May 13, 2019 16 / 17
73. Python 04 MMSC Challenge!
MMSC students have to work harder (as they get credit).
Project Euler is a series of mathematical and programming puzzles to
teach programming and number theory. Any programming language can
be used, but the code must run in under 10 seconds.
Problem 48 reads: the series 11 + 22 + · · · + 1010 = 10405071317. Find
the last 10 digits of the series 11 + 22 + · · · + 10001000.
Compute the answer in one line of Python that uses O(1) memory.
(Without googling.)
Email your one-liner and computed answer to
patrick.farrell@maths.ox.ac.uk.
P. E. Farrell (Oxford) Python IV May 13, 2019 17 / 17
74. Introduction to Python: object orientation
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python V May 13, 2019 1 / 16
75. Object orientation
Object orientation is a fundamental way to think about programming that
is often very useful for mathematical software.
P. E. Farrell (Oxford) Python V May 13, 2019 2 / 16
76. Object orientation
In mathematics, when we write sin, we refer to an object that we can
manipulate in various ways. For example, we can
◮ evaluate sin at a point x, returning a real number
◮ compute its derivative, returning another object cos
◮ compute the coefficients of its Taylor polynomial
These methods are shared between all sufficiently smooth functions.
Objects that share the same methods are grouped into classes.
P. E. Farrell (Oxford) Python V May 13, 2019 3 / 16
77. Object orientation
Given a class, we can instantiate it. sin is an instance of the class of
smooth functions. One of its attributes is its name. Another might be its
domain.
Consider a polynomial p(x). This is just like the sin function — every
function method also applies to p. But we can define other, special
methods for p that we cannot for sin: for example,
◮ return p’s coefficients, a list of real numbers
◮ factorise p, returning a list of polynomial factors
These methods define a class of polynomials, which inherit the methods of
the class of smooth functions, and add new ones on top.
P. E. Farrell (Oxford) Python V May 13, 2019 4 / 16
78. Object orientation
In mathematics, we often use the same symbol for different things. For
example, 5 + 4 and sin + cos have different meanings. But by using the
same symbol we express similarities of the mathematical operations.
So far we have introduced the concepts:
◮ classes
◮ instantiation
◮ inheritance
◮ methods
◮ attributes
◮ operator overloading
Next we’ll see how these work in Python.
P. E. Farrell (Oxford) Python V May 13, 2019 5 / 16
79. Defining classes
In Python, the class statement defines a new type:
class RationalNumber(object):
pass
Although this class doesn’t do much yet, we can instantiate it and
interrogate the objects created:
a = RationalNumber()
type(a)
class __main__.RationalNumber
isinstance(a, RationalNumber)
True
P. E. Farrell (Oxford) Python V May 13, 2019 6 / 16
80. Now we provide our example class with some attributes.
To start with, let’s define the __init__ method used for initialising the
class:
class RationalNumber(object):
def __init__(self, numerator, denominator):
self.numerator = numerator
self.denominator = denominator
Let’s see how this is used:
a = RationalNumber(5, 15)
a.numerator
5
a.denominator
15
The constructor sets two attributes.
P. E. Farrell (Oxford) Python V May 13, 2019 7 / 16
81. We can define other methods to do useful work with the objects.
class RationalNumber(object):
...
def add(self, other):
(p1, q1) = (self.numerator, self.denominator)
if isinstance(other, int):
(p2, q2) = (other, 1)
else:
(p2, q2) = (other.numerator, other.denominator)
return RationalNumber(p1*q2 + p2*q1, q1*q2)
Using this looks like:
p = RationalNumber(1, 2)
q = RationalNumber(1, 3)
r = q.add(p)
print(%s/%s % (r.numerator, r.denominator))
5/6
P. E. Farrell (Oxford) Python V May 13, 2019 8 / 16
82. It would be much nicer if we could just write q + p instead. To define the
plus sign for RationalNumber, we can define the __add__ method. (Just
rename the add method). This means that we can write:
p = RationalNumber(1, 2)
q = RationalNumber(1, 3)
r = q + p # alias for q.__add__(p)
We have overloaded the addition operator. In fact, that’s how addition is
defined for other types:
hasattr(float, '__add__')
True
hasattr(list, '__add__')
True
hasattr(dict, '__add__')
False
P. E. Farrell (Oxford) Python V May 13, 2019 9 / 16
83. The __init__ and __add__ methods are so-called special methods that
mean something to the Python object system. Here we’ll define another:
class RationalNumber(object):
...
def __float__(self):
return float(self.numerator) / float(self.denominator)
This is used by Python’s float command:
a = RationalNumber(5, 15)
float(a)
0.3333333333333333
P. E. Farrell (Oxford) Python V May 13, 2019 10 / 16
84. Special methods
Here’s a flavour of some of the special methods (google ’special method
names’ for the full list):
◮ __str__: return a string representation of the object
◮ __cmp__: called for obj other, obj other, etc
◮ __call__: called when the object is called as a function
◮ __mul__, __div__: obj * other, obj / other
◮ __pow__: implements obj**x
◮ __abs__: implements abs(obj)
◮ __contains__: implements x in obj
◮ __iter__: iterate over an object
◮ __getitem__, __setitem__: getting and setting obj[x]
P. E. Farrell (Oxford) Python V May 13, 2019 11 / 16
85. Subclassing
Consider one-step methods for solving an ODE. An explicit one-step
method construct the solution values ui by the recursion steps
ui+1 = ui + hφ(f, ui, ti, h)
This is an abstract description that characterises many algorithms: to
actually use it, we have to fill in the details for φ. Different concrete
algorithms make different choices:
◮ Explicit Euler: φ = f(ui, ti)
◮ Midpoint rule: φ = f(ui + h/2f(ui), ti + h/2)
◮ Runge-Kutta 4: φ = 1/6(s1 + 2s2 + 2s3 + s4)
We will model this in Python with an abstract base class that collects the
methods common to all one-step methods, and make subclasses to fill in
the parameters for the concrete method.
P. E. Farrell (Oxford) Python V May 13, 2019 12 / 16
86. from numpy import linspace
class OneStepMethod(object):
def __init__(self, f, x0, interval, N):
self.f = f
self.x0 = x0
self.interval = [t0, tT] = interval
self.grid = linspace(t0, tT, N)
self.h = (tT - t0) / N
def generate(self):
(ti, ui) = (self.grid[0], self.x0)
yield (ti, ui)
for t in self.grid[1:]:
ui = ui + self.h * self.step(self.f, ui, ti, self.h)
ti = t
yield (ti, ui)
def solve(self):
return list(self.generate())
def step(self, f, u, t, h):
raise NotImplementedError
P. E. Farrell (Oxford) Python V May 13, 2019 13 / 16
87. We can inherit from this type to specialise it:
class ExplicitEuler(OneStepMethod):
def step(self, f, u, t, h):
return f(u, t)
class MidPointRule(OneStepMethod):
def step(self, f, u, t, h):
return f(u + h/2.0 * f(u, t), t + h/2.0)
OneStepMethod is called the parent class. Any method or attribute that is
not overridden is inherited.
P. E. Farrell (Oxford) Python V May 13, 2019 14 / 16
88. When overriding, it is sometimes useful to access the attributes of the
parent class:
class NewmarkBeta(OneStepMethod):
def __init__(self, f, x0, interval, N, beta=0.25):
self.beta = beta
OneStepMethod.__init__(self, f, x0, interval, N)
Here, we override the constructor of the NewmarkBeta class, which in turn
must call the constructor of the subclass for the inherited methods to work.
P. E. Farrell (Oxford) Python V May 13, 2019 15 / 16
89. Python 05 Challenge!
Implement a Polynomial class with the following methods:
◮ Construction from coefficients in the monomial basis
◮ Addition
◮ Evaluation at a point
◮ Differentiation and integration (to give new polynomials)
◮ Roots
Test your code on examples with known answers (e.g. from Wolfram
Alpha).
Extra challenge: implement multiplication of two polynomials.
Does the code 5 + p work, where p is a Polynomial? Hint: look up the
difference between __add__ and __radd__.
P. E. Farrell (Oxford) Python V May 13, 2019 16 / 16
90. Day 1 Review Challenge!
Implement a recursive fibonacci(N) function that computes the Nth
Fibonacci number. Include a print statement to trace the execution.
Once that is working, memoise the recursive function with a decorator.
Compare the number of executions before and after for fibonacci(25).
P. E. Farrell (Oxford) Python VI May 13, 2019 1 / 12
91. Introduction to Python: errors and debugging
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python VI May 13, 2019 2 / 12
92. Exceptions
One error all programmers see is where code has incorrect syntax:
for i in range(10)
File stdin, line 1
for i in range(10)
^
SyntaxError: invalid syntax
This is an example of an exception being raised.
P. E. Farrell (Oxford) Python VI May 13, 2019 3 / 12
93. Exceptions
More examples of built-in exceptions:
1/0
Traceback (most recent call last):
File stdin, line 1, in module
ZeroDivisionError: integer division or modulo by zero
['a', 'b', 'c'][4]
Traceback (most recent call last):
File stdin, line 1, in module
IndexError: list index out of range
float(1.0 + 1.0j)
Traceback (most recent call last):
File stdin, line 1, in module
TypeError: can't convert complex to float
P. E. Farrell (Oxford) Python VI May 13, 2019 4 / 12
94. Exceptions
Creating an error is called raising an exception. You can raise an exception
with the raise statement:
raise Exception(Something went wrong)
For example, we might
def factorial(n):
if n 0:
raise ValueError(A nonnegative integer is expected)
...
Why do this over printing an error message?
◮ Print statements are easy to miss, if the message is buried in many
other messages.
◮ With a print statement, calling code won’t know something went
wrong, and handle it.
P. E. Farrell (Oxford) Python VI May 13, 2019 5 / 12
95. Exceptions
Dealing with exceptions is referred to a catching an exception. We use
three statements for this: try, except, finally.
try:
f = open(data.txt, r)
data = f.readline()
value = float(data)
except IOError:
print(Caught an IOError! Maybe couldn't open the file?)
except ValueError:
print(Caught a ValueError! Maybe couldn't convert to float?)
finally:
f.close()
P. E. Farrell (Oxford) Python VI May 13, 2019 6 / 12
96. Exceptions
You can also define your own exceptions:
class ConvergenceError(Exception):
pass
# later on ...
if iters max_iters:
raise ConvergenceError(Algorithm did not converge)
P. E. Farrell (Oxford) Python VI May 13, 2019 7 / 12
97. Context managers
There is a very useful construct in Python for simplifying exception
handling when working with “contexts” like files or databases: the with
statement.
with open(data.txt, r) as f:
# process f
The open function returns a context manager that ensures that the file
handle is closed when the block terminates.
P. E. Farrell (Oxford) Python VI May 13, 2019 8 / 12
98. Context managers
Context managers are defined by two special methods: __enter__ and
__exit__. Here are some more examples, from threaded parallelism:
import threading
lock = threading.Lock()
with lock:
# do something with the protected resource
and from connecting to a database:
import sqlite
with sqlite.connect(db) as conn:
query = INSERT INTO table VALUES ('hello', 'world')
conn.execute(query)
P. E. Farrell (Oxford) Python VI May 13, 2019 9 / 12
99. Context managers
Numerically, one of the more useful is numpy’s errstate:
from numpy import errstate, sqrt
with errstate(invalid='ignore'):
print(sqrt(-1)) # prints 'nan'
with errstate(invalid='warn'):
print(sqrt(-1)) # prints 'nan' and 'RuntimeWarning'
with errstate(invalid='raise'):
print(sqrt(-1)) # raises FloatingPointError
P. E. Farrell (Oxford) Python VI May 13, 2019 10 / 12
100. Debugging
When an Exception is raised, you see the call stack. The call stack
contains the trace of all the functions that called the code where the
exception was raised. Consider the fle callstack.py:
def f():
g()
def g():
h()
def h():
1/0
f()
This yields the stack trace
Traceback (most recent call last):
File callstack.py, line 10, in module
f()
File callstack.py, line 2, in f
g()
File callstack.py, line 5, in g
h()
File callstack.py, line 8, in h
1/0
ZeroDivisionError: integer division or modulo by zero
P. E. Farrell (Oxford) Python VI May 13, 2019 11 / 12
101. Debugging
Start the debugger by calling pdb.set_trace at the appropriate point in
your code:
import pdb
from math import sqrt, atan2
def complex_to_polar(z):
pdb.set_trace()
r = sqrt(z.real**2 + z.imag**2)
phi = atan2(z.imag, z.real)
return (r, phi)
complex_to_polar(3 + 5j)
This starts a debugger at the specified line:
[pef@aislinn:/tmp]$ python bugs.py
/tmp/showpdb.py(6)complex_to_polar()
- r = sqrt(z.real**2 + z.imag**2)
(Pdb)
P. E. Farrell (Oxford) Python VI May 13, 2019 12 / 12
102. Introduction to Python: NumPy
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python VII May 13, 2019 1 / 15
103. NumPy
Much of the scientific stack for Python is built on top of numpy. numpy
provides highly optimised implementations of the fundamental datatypes
for linear algebra: vectors, matrices, and their higher-dimensional
analogues.
import numpy as np
a = np.array([0, 1, 2, 3])
a
array([0, 1, 2, 3])
Key differences from lists:
◮ Fixed data type
◮ Fixed size =⇒ vectorisation
◮ Much, much faster
P. E. Farrell (Oxford) Python VII May 13, 2019 2 / 15
104. NumPy
b = np.arange(1, 4, 0.5) # start, stop, step
b
array([ 1. , 1.5, 2. , 2.5, 3. , 3.5])
c = np.linspace(0, 1, 6) # start, step, num-points
c
array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])
a = np.ones((3, 3)) # reminder: (3, 3) is a tuple
a
array([[ 1., 1., 1.],
[ 1., 1., 1.],
[ 1., 1., 1.]])
b = np.zeros((2, 2))
b
array([[ 0., 0.],
[ 0., 0.]])
d = np.diag(np.array([1, 2, 3, 4]))
d
array([[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4]])
P. E. Farrell (Oxford) Python VII May 13, 2019 3 / 15
105. Datatypes
Numpy arrays have a datatype. With the array constructor, numpy creates
arrays with the smallest datatype that can contain the given data.
a = np.array([1, 2, 3])
a.dtype
dtype('int64')
a[0] = 0.5 # danger! will be cast to int64
a
array([0, 2, 3])
b = np.array([1., 2., 3.])
b.dtype
dtype('float64')
P. E. Farrell (Oxford) Python VII May 13, 2019 4 / 15
106. Slicing
Numpy arrays can be sliced in all sorts of powerful ways:
M = np.random.randn(10, 10)
M[2:4, 5:8]
array([[ 0.57563161, -1.81466408, 0.98266811],
[-2.40016935, -0.42627965, 0.80222344]])
M[2:4, 5:8] = 0.0
These simple slices create views into the dataset.
P. E. Farrell (Oxford) Python VII May 13, 2019 5 / 15
107. Example: Sieve of Eratosthenes
import numpy as np
def primes(N):
is_prime = np.ones(N, dtype=bool)
is_prime[:2] = False # 0, 1 not prime
N_sqrt = int(np.sqrt(N))
for j in range(2, N_sqrt):
is_prime[2*j::j] = False
return np.nonzero(is_prime)[0]
if __name__ == __main__:
import sys
N = int(sys.argv[1]) # read in from command-line
print(primes(N))
P. E. Farrell (Oxford) Python VII May 13, 2019 6 / 15
108. Fancy indexing
Numpy arrays can also be indexed with Boolean or integer arrays (masks).
These create copies, not views.
a = np.random.random_integers(0, 20, 15)
a
array([10, 3, 8, 0, 19, 10, 11, 9, 10, 6, 0, 20, 12, 7, 14])
(a % 3 == 0)
array([False, True, False, True, False, False, False, True, False,
True, True, False, True, False, False], dtype=bool)
mask = (a % 3 == 0)
extract_from_a = a[mask] # or, a[a%3==0]
extract_from_a # extract a sub-array with the mask
array([ 3, 0, 9, 6, 0, 12])
We actually saw this earlier in plotting the Mandelbrot fractal.
P. E. Farrell (Oxford) Python VII May 13, 2019 7 / 15
109. Operations on arrays
All arithmetic operates elementwise.
a = np.array([1, 2, 3, 4])
a + 1
array([2, 3, 4, 5])
2**a
array([ 2, 4, 8, 16])
b = np.ones(4) + 1
a - b
array([-1., 0., 1., 2.])
a * b
array([ 2., 4., 6., 8.])
These operations are vectorised: much faster than if implemented in
Python.
P. E. Farrell (Oxford) Python VII May 13, 2019 8 / 15
110. More examples
a = np.array([1, 1, 0, 0], dtype=bool)
b = np.array([1, 0, 1, 0], dtype=bool)
a == b
array([ True, False, False, True], dtype=bool)
a b
array([False, True, False, False], dtype=bool)
a b
array([ True, False, False, False], dtype=bool)
a | b
array([ True, True, True, False], dtype=bool)
np.sin(a) # elementwise
array([ 0.84130859, 0.84130859, 0. , 0. ])
P. E. Farrell (Oxford) Python VII May 13, 2019 9 / 15
111. Reductions
x = np.array([[1, 1], [2, 2]])
x
array([[1, 1],
[2, 2]])
x.sum(axis=0) # columns (first dimension)
array([3, 3])
x[:, 0].sum(), x[:, 1].sum()
(3, 3)
x.sum(axis=1) # rows (second dimension)
array([2, 4])
x[0, :].sum(), x[1, :].sum()
(2, 4)
P. E. Farrell (Oxford) Python VII May 13, 2019 10 / 15
113. Shape manipulation
a = np.array([[1, 2, 3], [4, 5, 6]])
a.ravel()
array([1, 2, 3, 4, 5, 6])
a.T
array([[1, 4],
[2, 5],
[3, 6]])
a.T.ravel()
array([1, 4, 2, 5, 3, 6])
P. E. Farrell (Oxford) Python VII May 13, 2019 12 / 15
114. Shape manipulation
a.shape
(2, 3)
b = a.ravel()
b = b.reshape((2, 3))
b
array([[1, 2, 3],
[4, 5, 6]])
b[0, 0] = 99
a
array([[99, 2, 3],
[ 4, 5, 6]])
P. E. Farrell (Oxford) Python VII May 13, 2019 13 / 15
115. Python 07 Challenge!
Conway’s Game of Life is a cellular automaton that creates beautiful
patterns. Given an N × N grid of cells, each cell is either dead (0) or alive
(1). Every cell interacts with its eight neighbours. At each step in time,
the following transitions occur:
◮ Any live cell with fewer than two neighbours dies (underpopulation).
◮ Any live cell with two or three neighbours lives on (survival).
◮ Any live cell with more than three neighbours dies (overcrowding).
◮ Any dead cell with exactly three live neighbours becomes alive
(reproduction).
Represent the N × N grid with an (N + 2) × (N + 2) numpy matrix bordered by
zeros. Implement:
◮ a neighbours function that counts the live neighbours of each cell.
◮ an iterate function that applies the rules.
◮ call your code on the glider pattern [[0, 0, 1], [1, 0, 1], [0, 1, 1]].
P. E. Farrell (Oxford) Python VII May 13, 2019 14 / 15
116. P. E. Farrell (Oxford) Python VII May 13, 2019 15 / 15
117. Introduction to Python: SciPy
Patrick Farrell
MMSC: Python in Scientific Computing
May 13, 2019
P. E. Farrell (Oxford) Python VIII May 13, 2019 1 / 9
118. SciPy
NumPy provides the core dense multidimensional array class for Python.
SciPy provides basically everything else:
scipy.linalg Linear algebra
scipy.integrate Quadrature
scipy.fftpack Fourier transforms
scipy.io I/O
scipy.sparse Sparse matrices
scipy.interpolate Interpolation
scipy.optimize Optimisation
scipy.stats Statistics
Let’s take a closer look at some of them.
P. E. Farrell (Oxford) Python VIII May 13, 2019 2 / 9
119. Dense linear algebra
Determinants:
from scipy import linalg
arr = np.array([[1, 2],
... [3, 4]])
linalg.det(arr)
-2.0
arr = np.array([[3, 2],
... [6, 4]])
linalg.det(arr)
0.0
linalg.det(np.ones((3, 4)))
Traceback (most recent call last):
...
ValueError: expected square matrix
P. E. Farrell (Oxford) Python VIII May 13, 2019 3 / 9
120. Dense linear algebra
Matrix inverses:
arr = np.array([[1, 2],
... [3, 4]])
iarr = linalg.inv(arr)
iarr
array([[-2. , 1. ],
[ 1.5, -0.5]])
np.allclose(np.dot(arr, iarr), np.eye(2))
True
P. E. Farrell (Oxford) Python VIII May 13, 2019 4 / 9
121. Dense linear algebra
Singular value decompositions:
H = linalg.hilbert(3)
(U, L, VT) = linalg.svd(H)
L
array([ 1.40831893, 0.12232707, 0.00268734])
P. E. Farrell (Oxford) Python VIII May 13, 2019 5 / 9
122. Dense linear algebra
Some more routines (see help(scipy.linalg) for the full list):
solve Solve with LU
norm Matrix and vector norms
pinv Moore–Penrose pseudoinverse
eig Eigenvalues
qr QR factorisation
expm Matrix exponentiation
P. E. Farrell (Oxford) Python VIII May 13, 2019 6 / 9
123. Sparse linear algebra
Almost all applications in scientific computing involve sparse matrices.
scipy.sparse implements several efficient sparse matrix data formats.
You should be aware of two:
◮ List of lists format (lil): efficient for construction and
modification
◮ Compressed sparse row format (csr): efficient for matvec and
sparse LU
P. E. Farrell (Oxford) Python VIII May 13, 2019 7 / 9
124. Example: 1D Laplacian with homogeneous Dirichlet BCs
import scipy.sparse as sp
import scipy.sparse.linalg as la
import numpy as np
N = 1000; h = 1.0 / (N+1)
K = sp.diags(diagonals=[2, -1, -1], offsets=[0, -1, 1],
shape=(N, N), format=lil)
rhs = 2 * np.ones(N) * h**2
K[+0,:] = 0.0; K[+0, +0] = 1.0
K[-1,:] = 0.0; K[-1, -1] = 1.0
rhs[+0] = 0.0; rhs[-1] = 0.0
K = K.tocsr()
u = la.spsolve(K, rhs)
P. E. Farrell (Oxford) Python VIII May 13, 2019 8 / 9
125. Python 08 Challenge!
Use Newton’s method and finite differences to calculate the solution of
ε2
y′′
+ 2(1 − x2
)y + y2
= 1, y(−1) = y(1) = 0,
for ε = 10−1.
Newton–Kantorovich algorithm:
◮ Start with initial guess y0(x) = 0.
◮ For k = 0, . . . , solve
ε2
δy′′
+ 2(1 − x2
)δy + 2ykδy = 1 − ε2
y′′
k − 2(1 − x2
)yk − y2
k
with boundary conditions δy(−1) = δy(+1) = 0.
◮ Set yk+1 = yk + 0.9δy.
P. E. Farrell (Oxford) Python VIII May 13, 2019 9 / 9