Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Introduction to Python: preliminaries Patrick Farrell MMSC: Python in Scientific Computing June 10, 2015 P. E. Farrell (Oxford) Python 0 June 10, 2015 1/5 Rough schedule Monday Tuesday Wednesday Thursday Friday Morning Afternoon Datatypes, functions Object orientation, debugging SciPy, finite differences p-Laplace, hyperelasticity Navier–Stokes Modules, plotting, iteration NumPy arrays Poisson, heat eqn Stokes Optimal control/KKT P. E. Farrell (Oxford) Python 0 June 10, 2015 2/5 Rough schedule Monday Tuesday Wednesday Thursday Friday Morning Afternoon Datatypes, functions Object orientation, debugging SciPy, finite differences p-Laplace, hyperelasticity Navier–Stokes Modules, plotting, iteration NumPy arrays Poisson, heat eqn Stokes Optimal control/KKT P. E. Farrell (Oxford) Python 0 June 10, 2015 2/5 Timetable Basic plan: 10:00 — 12:30; 14:00 — 17:00. I’ll usually be around here after. Exceptions: Finish at 16:00 on Friday afternoon. P. E. Farrell (Oxford) Python 0 June 10, 2015 3/5 Comments I No TAs: please help each other! (Except the exercises that are submitted via email) I Feedback (good or bad) welcome: [email protected] P. E. Farrell (Oxford) Python 0 June 10, 2015 4/5 Preview: final challenge! MMSC: for this to count for your MSc, you have to write a final report. Your task: I Implement a finite element discretisation of a PDE. I Take a discretisation and configuration from the literature, or invent your own. I Bonus points for: nonlinearity, coupling, mathematical interest, . . . I Include an MMS verification. I Submit working code and a report (≤ 20 pages) discussing the PDE and its implementation. I Inspiration: your summer projects; the PDE coffee table book. I Deadline: 6 July 2015. P. E. Farrell (Oxford) Python 0 June 10, 2015 5/5 Introduction to Python: native datatypes Patrick Farrell MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) Python I May 6, 2015 1 / 21 What is Python? I Very high-level language I Powerful complex data structures I Supports imperative, functional, object-oriented programming I Dynamic, extremely rapid development I Free (very useful for parallel) I Main scripting language used in large-scale scientific computing I Beautiful, and fun . . . P. E. Farrell (Oxford) Python I May 6, 2015 2 / 21 P. E. Farrell (Oxford) Python I May 6, 2015 3 / 21 Running Python Option 1: run interactively Option 2: put code in a file and run $ ipython In [1]: print("Hello, world") Hello, world $ cat hello.py print("Hello, world") $ python hello.py Hello, world (mainly for development) (mainly for production runs) P. E. Farrell (Oxford) Python I May 6, 2015 4 / 21 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 P. E. Farrell (Oxford) >>> 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!’] Python I May 6, 2015 5 / 21 Integer division Be aware of integer division (à la C/C++): >>> 0 >>> >>> 0.5 >>> 0.5 1/2 # ?!?! 1.0/2 1.0/2.0 Use // to unambiguously specify integer division: >>> 1.0//2.0 0.0 Python has a command line to make this issue a warning: $ python -Q warn -c "print 1/2" -c:1: DeprecationWarning: classic int division P. E. Farrell (Oxford) Python I May 6, 2015 6 / 21 Integer promotion def factorial(n): out = 1 while n >= 1: out = out * n n = n - 1 return out print type(factorial(20)) print type(factorial(21)) produces <type ’int’> <type ’long’> Very useful! P. E. Farrell (Oxford) Python I May 6, 2015 7 / 21 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 6, 2015 8 / 21 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 6, 2015 9 / 21 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 6, 2015 10 / 21 String interpolation Strings can be interpolated: >>> >>> Our >>> >>> Pi: profit = 90210 print("Our annual profit was: EUR %s." % profit) annual profit was: EUR 90210. from math import pi print("Pi: %1.5f" % pi) 3.14159 P. E. Farrell (Oxford) Python I May 6, 2015 11 / 21 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 6, 2015 12 / 21 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 6, 2015 13 / 21 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 6, 2015 14 / 21 List iteration Lists >>> >>> ... ... >>> 35 >>> 35 can be iterated over: res = 0 for i in l: res = res + i res sum(l) P. E. Farrell (Oxford) Python I May 6, 2015 15 / 21 List comprehension In mathematics, we write statements like: V = x2 : x ∈ S In Python, we can write analogous code: list comprehensions. >>> >>> [0, >>> [0, l = range(4) [x**2 for x in l] 1, 4, 9] [x**2 for x in l if x % 2 == 0] 4] P. E. Farrell (Oxford) Python I May 6, 2015 16 / 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 6, 2015 17 / 21 Dictionaries The dictionary can natively map different types to different types. >>> mydict = {’a’: 1, True: "hello"} >>> P. E. Farrell (Oxford) Python I May 6, 2015 18 / 21 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: I to pass arguments into functions I to return values from functions I as keys in dictionaries. P. E. Farrell (Oxford) Python I May 6, 2015 19 / 21 Sets Our final in-built container type is the set. >>> {1, True >>> {1, True >>> {1, set([1, 2, 3, 3, 2} == {1, 2, 3} 2, 3} == {1, 3, 2} 3, 5}.union({2, 4}) 2, 3, 4, 5]) P. E. Farrell (Oxford) Python I May 6, 2015 20 / 21 Python 01 Challenge! Consider the recursion formula un+3 = un+2 + ha 23 4 5 un+2 − un+1 + un , 12 3 12 with n = 0, . . . , 1000, h = 1/1000, a = −1/2, u0 = exp (0), u1 = exp (ha), u2 = exp (2ha). (a) Create a list approx with the values of u, starting with the three given values and completing it with the recursion formula. (b) Create another list exact with values exp (anh). (c) Create another list error with the difference between the two lists. Hints: from math import exp for (e, a) in zip(exact, approx): ... P. E. Farrell (Oxford) Python I May 6, 2015 21 / 21 Introduction to Python: functions Patrick Farrell MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) Python II May 6, 2015 1 / 15 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 6, 2015 2 / 15 Input to functions Default values for parameters can be specified: def slicer(seq, start=None, stop=None, step=None): return seq[start:stop:step] seq = [str(i) for i in range(5)] print(slicer(seq)) # [’0’, ’1’, ’2’, ’3’, ’4’] print(slicer(seq, start=2)) # [’2’, ’3’, ’4’] print(slicer(seq, start=2, stop=4)) # [’2’, ’3’] You can also mix positional and keyword arguments: slicer(seq, 4, step=-1, stop=1) # [’4’, ’3’, ’2’] We can also pass arguments in a dictionary: d = {’start’: 4, ’stop’: 1, ’step’: -1} slicer(seq, **d) # [’4’, ’3’, ’2’] P. E. Farrell (Oxford) Python II May 6, 2015 3 / 15 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 6, 2015 4 / 15 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 6, 2015 5 / 15 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 6, 2015 6 / 15 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 6, 2015 7 / 15 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 6, 2015 8 / 15 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) 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 6, 2015 9 / 15 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 6, 2015 10 / 15 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 6, 2015 11 / 15 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 6, 2015 12 / 15 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 6, 2015 13 / 15 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 6, 2015 14 / 15 Python 02 Challenge! (a) The previous challenge implemented the 3rd-order Adams-Bashforth algorithm on the ODE du = au, u(0) = 1.0, u : [0, 1] 7→ R dt Write a function ab3(f, u0, t0, tT, N) that solves an ODE du = f (u), u(0) = u0 , u : [t0 , tT ] 7→ R dt using the 3rd-order Adams-Bashforth discretisation. (u0 is a tuple with the first 3 values.) (b) The greatest common divisor of two integers can be computed using Euclid’s algorithm: ( a, if b = 0 gcd(a, b) = gcd(b, a mod b), otherwise Implement a function gcd(a, b). P. E. Farrell (Oxford) Python II May 6, 2015 15 / 15 Introduction to Python: modules, namespaces and plotting Patrick Farrell MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) Python III May 6, 2015 1 / 15 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 6, 2015 2 / 15 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 6, 2015 3 / 15 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 6, 2015 4 / 15 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 6, 2015 5 / 15 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 6, 2015 6 / 15 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 6, 2015 7 / 15 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 6, 2015 8 / 15 Plotting sin(x) 1.0 0.5 0.0 0.5 1.0 8 P. E. Farrell (Oxford) 6 4 2 0 Python III 2 4 6 8 May 6, 2015 9 / 15 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 6, 2015 10 / 15 Plotting 6 5 Polynomial fitting degree 2 degree 4 4 3 2 1 01 P. E. Farrell (Oxford) 0 1 2 Python III 3 4 5 May 6, 2015 11 / 15 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 6, 2015 12 / 15 Plotting P. E. Farrell (Oxford) Python III May 6, 2015 13 / 15 Plotting from mpl_toolkits.basemap import Basemap from matplotlib.pyplot import * m = Basemap(projection=’merc’, llcrnrlat=-10, urcrnrlat=80, llcrnrlon=-140, urcrnrlon=100, lat_ts=40, resolution=’h’, area_thresh=10000) m.drawcoastlines() m.drawcountries() m.fillcontinents() (ny_lat, ny_lon) = (40.6397, -73.7789) (lon_lat, lon_lon) = (51.4775, 0.4614) m.drawgreatcircle(lon_lon, lon_lat, ny_lon, ny_lat, linewidth=3) savefig("greatcircle.pdf") P. E. Farrell (Oxford) Python III May 6, 2015 14 / 15 Plotting P. E. Farrell (Oxford) Python III May 6, 2015 15 / 15 Introduction to Python: iteration Patrick Farrell MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) Python IV May 6, 2015 1 / 17 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 6, 2015 2 / 17 break, continue and pass Sometimes we need to exit loops early. For example, interacting with users could look like: while True: s = raw_input("Enter a string (quit to quit): ") if s == "quit": break print("String length: %s" % len(s)) P. E. Farrell (Oxford) Python IV May 6, 2015 3 / 17 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 6, 2015 4 / 17 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 6, 2015 5 / 17 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 6, 2015 6 / 17 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 6, 2015 7 / 17 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: 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 P. E. Farrell (Oxford) Python IV May 6, 2015 8 / 17 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 6, 2015 9 / 17 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 6, 2015 10 / 17 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 6, 2015 11 / 17 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 6, 2015 12 / 17 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 6, 2015 13 / 17 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 6, 2015 14 / 17 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 6, 2015 15 / 17 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 (−1)n = , 2n + 1 n=0 which converges to π/4 (slowly!). Hint: value = next(sequence). P. E. Farrell (Oxford) Python IV May 6, 2015 16 / 17 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 [email protected]. P. E. Farrell (Oxford) Python IV May 6, 2015 17 / 17 Introduction to Python: object orientation Patrick Farrell MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) Python V May 6, 2015 1 / 16 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 6, 2015 2 / 16 Object orientation In mathematics, when we write sin, we refer to an object that we can manipulate in various ways. For example, we can I evaluate sin at a point x, returning a real number I compute its derivative, returning another object cos I 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 6, 2015 3 / 16 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, I return p’s coefficients, a list of real numbers I 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 6, 2015 4 / 16 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: I classes I instantiation I inheritance I methods I attributes I operator overloading Next we’ll see how these work in Python. P. E. Farrell (Oxford) Python V May 6, 2015 5 / 16 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 6, 2015 6 / 16 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 >>> >>> 5 >>> 15 see how this is used: a = RationalNumber(5, 15) a.numerator a.denominator The constructor sets two attributes. P. E. Farrell (Oxford) Python V May 6, 2015 7 / 16 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 6, 2015 8 / 16 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 6, 2015 9 / 16 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 6, 2015 10 / 16 Special methods Here’s a flavour of some of the special methods (google ’special method names’ for the full list): I __str__: return a string representation of the object I __cmp__: called for obj < other, obj > other, etc I __call__: called when the object is called as a function I __mul__, __div__: obj * other, obj / other I __pow__: implements obj**x I __abs__: implements abs(obj) I __contains__: implements x in obj I __iter__: iterate over an object I __getitem__, __setitem__: getting and setting obj[x] P. E. Farrell (Oxford) Python V May 6, 2015 11 / 16 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: I Explicit Euler: φ = f (ui , ti ) I Midpoint rule: φ = f (ui + h/2f (ui ), ti + h/2) I 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 6, 2015 12 / 16 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 6, 2015 13 / 16 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 6, 2015 14 / 16 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 6, 2015 15 / 16 Python 05 Challenge! Implement a Polynomial class with the following methods: I Construction from coefficients, or from x–y values I Addition I Evaluation at a point I Differentiation and integration (to give new polynomials) I 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 6, 2015 16 / 16 Day 1 Review Challenge! Implement a recursive fibonacci(N) function that computes the N th 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 6, 2015 1 / 12 Introduction to Python: errors and debugging Patrick Farrell MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) Python VI May 6, 2015 2 / 12 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 6, 2015 3 / 12 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 6, 2015 4 / 12 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? I Print statements are easy to miss, if the message is buried in many other messages. I With a print statement, calling code won’t know something went wrong, and handle it. P. E. Farrell (Oxford) Python VI May 6, 2015 5 / 12 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 6, 2015 6 / 12 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 6, 2015 7 / 12 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 6, 2015 8 / 12 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 6, 2015 9 / 12 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 6, 2015 10 / 12 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 6, 2015 11 / 12 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 6, 2015 12 / 12 Introduction to Python: NumPy Patrick Farrell MMSC: Python in Scientific Computing May 7, 2015 P. E. Farrell (Oxford) Python VII May 7, 2015 1 / 15 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: I Fixed data type I Fixed size =⇒ vectorisation I Much, much faster P. E. Farrell (Oxford) Python VII May 7, 2015 2 / 15 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 7, 2015 3 / 15 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 7, 2015 4 / 15 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, [-2.40016935, -0.42627965, >>> M[2:4, 5:8] = 0.0 0.98266811], 0.80222344]]) These simple slices create views into the dataset. P. E. Farrell (Oxford) Python VII May 7, 2015 5 / 15 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 7, 2015 6 / 15 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 7, 2015 7 / 15 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 7, 2015 8 / 15 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 7, 2015 ]) 9 / 15 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 7, 2015 10 / 15 Broadcasting . np arange(3)+5 + 0 1 2 . 5 5 5 = 5 6 7 = 1 2 3 1 2 3 1 2 3 , 3))+np.arange(3) np ones((3 1 1 1 1 1 1 1 1 1 . + . 0 1 2 0 1 2 0 1 2 , 1))+np.arange(3) np arange(3) reshape((3 0 0 0 1 1 1 2 2 2 P. E. Farrell (Oxford) + 0 1 2 0 1 2 0 1 2 Python VII = 0 1 2 1 2 3 2 3 4 May 7, 2015 11 / 15 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 7, 2015 12 / 15 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 7, 2015 13 / 15 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: I Any live cell with fewer than two neighbours dies (underpopulation). I Any live cell with two or three neighbours lives on (survival). I Any live cell with more than three neighbours dies (overcrowding). I 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: I a neighbours function that counts the live neighbours of each cell. I an iterate function that applies the rules. I call your code on the glider pattern [[0, 0, 1], [1, 0, 1], [0, 1, 1]]. P. E. Farrell (Oxford) Python VII May 7, 2015 14 / 15 P. E. Farrell (Oxford) Python VII May 7, 2015 15 / 15 Introduction to Python: SciPy Patrick Farrell MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) Python VIII May 6, 2015 1 / 10 SciPy NumPy provides the core dense multidimensional array class for Python. SciPy provides basically everything else: scipy.linalg scipy.integrate scipy.fftpack scipy.io scipy.sparse scipy.interpolate scipy.optimize scipy.stats Linear algebra Quadrature Fourier transforms I/O Sparse matrices Interpolation Optimisation Statistics Let’s take a closer look at some of them. P. E. Farrell (Oxford) Python VIII May 6, 2015 2 / 10 Interoperability with MATLAB >>> from scipy import io as spio >>> a = np.ones((3, 3)) >>> spio.savemat(’file.mat’, {’a’: a}) >>> data = spio.loadmat(’file.mat’) >>> data[’a’] array([[ 1., 1., 1.], [ 1., 1., 1.], [ 1., 1., 1.]]) Note: for .mat 7.3 format, use HDF5 via h5py. P. E. Farrell (Oxford) Python VIII May 6, 2015 3 / 10 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 6, 2015 4 / 10 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 6, 2015 5 / 10 Dense linear algebra Singular value decompositions: >>> H = linalg.hilbert(3) >>> (U, L, VT) = linalg.svd(H) >>> L array([ 1.40831893, 0.12232707, P. E. Farrell (Oxford) Python VIII 0.00268734]) May 6, 2015 6 / 10 Dense linear algebra Some more routines (see help(scipy.linalg) for the full list): solve norm pinv eig qr expm P. E. Farrell (Oxford) Solve with LU Matrix and vector norms Moore–Penrose pseudoinverse Eigenvalues QR factorisation Matrix exponentiation Python VIII May 6, 2015 7 / 10 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: I List of lists format ("lil"): efficient for construction and modification I Compressed sparse row format ("csr"): efficient for matvec and sparse LU P. E. Farrell (Oxford) Python VIII May 6, 2015 8 / 10 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[-1,:] = 0.0; rhs[+0] = 0.0; K[+0, +0] = 1.0 K[-1, -1] = 1.0 rhs[-1] = 0.0 K = K.tocsr() u = la.spsolve(K, rhs) P. E. Farrell (Oxford) Python VIII May 6, 2015 9 / 10 Python 08 Challenge! Consider the one-dimensional Gray–Scott equations: ut = u ∆u − uv 2 + F (1 − u) vt = v ∆v + uv 2 − (c + F )v with periodic boundary conditions on [−1, 1). (a) Solve the zero-diffusion case with c = 0.065, F = 0.06 with forward Euler, for various initial conditions. Plot the steady-state solutions. (b) Take u = 6 × 10−5 , v = 2 × 10−5 . Use the initial condition v(x) = χ(x) + 0.05η(x) u(x) = 1 − v(x) where η(x) ∼ N (0, 1) and ( 1 if |x − 0.1| ≤ 0.1 χ(x) = 0 otherwise P. E. Farrell (Oxford) Python VIII May 6, 2015 10 / 10 Implementing finite element models in Python/FEniCS: static linear PDEs Patrick Farrell, Hans Petter Langtangen, Anders Logg, Marie Rognes, André Massing MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) FEniCS I May 6, 2015 1 / 17 FEniCS is an automated programming environment for differential equations I C++/Python library I Initiated 2003 in Chicago I 1000–2000 monthly downloads I Licensed under the GNU LGPL http://fenicsproject.org/ Collaborators Simula Research Laboratory, University of Cambridge, University of Chicago, Texas Tech University, KTH Royal Institute of Technology, Chalmers University of Technology, Imperial College London, University of Oxford, . . . P. E. Farrell (Oxford) FEniCS I May 6, 2015 2 / 17 Poisson: the Hello World of PDEs Consider Poisson’s equation with Dirichlet boundary conditions: −∆u = f in Ω u=g on ∂Ω Poisson’s equation arises in numerous applications: I heat conduction, electrostatics, diffusion of substances, twisting of elastic rods, inviscid fluid flow, water waves, magnetostatics, . . . I as part of numerical splitting strategies for more complicated systems of PDEs, e.g. the Navier–Stokes equations, bidomain equations, Cahn-Hilliard P. E. Farrell (Oxford) FEniCS I May 6, 2015 3 / 17 From PDE to variational problem We multiply the PDE by a test function v and integrate over Ω: Z Z − (∆u)v dx = f v dx Ω Ω Then integrate by parts and set v = 0 on the Dirichlet boundary: Z Z Z ∂u v ds − (∆u)v dx = ∇u · ∇v dx − Ω Ω ∂Ω ∂n | {z } =0 In weak form, the equation is: Z Z f v dx ∇u · ∇v dx = Ω P. E. Farrell (Oxford) Ω FEniCS I May 6, 2015 4 / 17 A test problem We construct a test problem for which we can easily check the answer. We first define the exact solution by u(x, y) = 1 + x2 + 2y 2 We insert this into Poisson’s equation: f = −∆u = −∆(1 + x2 + 2y 2 ) = −(2 + 4) = −6 This technique is called the method of manufactured solutions (MMS). P. E. Farrell (Oxford) FEniCS I May 6, 2015 5 / 17 Hello World in FEniCS: implementation from dolfin import * mesh = UnitSquareMesh(32, 32) V = FunctionSpace(mesh, "Lagrange", 1) u = Function(V) v = TestFunction(V) f = Constant(-6.0) g = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]") bc = DirichletBC(V, g, DomainBoundary()) F = inner(grad(u), grad(v))*dx - f*v*dx solve(F == 0, u, bc) plot(u, interactive=True) P. E. Farrell (Oxford) FEniCS I May 6, 2015 6 / 17 Step by step: the first line The first line of a FEniCS program usually begins with from dolfin import * This imports key classes like UnitSquareMesh, FunctionSpace, Function and so forth, from the FEniCS user interface (DOLFIN). P. E. Farrell (Oxford) FEniCS I May 6, 2015 7 / 17 Step by step: creating a mesh Next, we create a mesh of our domain Ω: mesh = UnitSquareMesh(32, 32) defines a (triangular) mesh with 32 elements along each edge. Other useful classes for creating meshes include UnitIntervalMesh, UnitCubeMesh, UnitCircleMesh, UnitSphereMesh, RectangleMesh and BoxMesh. Complex geometries should be built in dedicated mesh generation tools and imported: mesh = Mesh("complexmesh.xdmf") Mesh generation is a huge subject in its own right, outside the scope of this course. P. E. Farrell (Oxford) FEniCS I May 6, 2015 8 / 17 Step by step: creating a function space The following line creates a function space on Ω: V = FunctionSpace(mesh, "Lagrange", 1) The second argument specifies the type of element, while the third argument is the degree of the basis functions on the element. Other types of elements include "Discontinuous Lagrange", "Brezzi-Douglas-Marini", "Raviart-Thomas", "Crouzeix-Raviart", "Nedelec 1st kind H(curl)" and "Nedelec 2nd kind H(curl)". See help(FunctionSpace) for a list. P. E. Farrell (Oxford) FEniCS I May 6, 2015 9 / 17 Step by step: defining expressions Next, we define an expression for the boundary value: g = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]") The formula must be written in C++ syntax. Optionally, a polynomial degree may be specified g = Expression("1 + x[0]*x[0] + 2*x[1]*x[1]", degree=2) The Expression class is very flexible and can be used to create complex user-defined expressions. For more information, try help(Expression). P. E. Farrell (Oxford) FEniCS I May 6, 2015 10 / 17 Step by step: defining a boundary condition The following code defines a Dirichlet boundary condition: bc = DirichletBC(V, g, DomainBoundary()) This boundary condition states that a function in the function space defined by V should be equal to g on the domain defined by DomainBoundary(). Note that the above line does not yet apply the boundary condition to all functions in the function space. (It gets enforced strongly during solve.) P. E. Farrell (Oxford) FEniCS I May 6, 2015 11 / 17 Step by step: more about defining domains For a Dirichlet boundary condition, a simple domain can be defined by a string "on_boundary" # The entire boundary Alternatively, domains can be defined by subclassing SubDomain class Boundary(SubDomain): def inside(self, x, on_boundary): return on_boundary You may want to experiment with the definition of the boundary: "near(x[0], 0.0)" # x_0 = 0 "near(x[0], 0.0) || near(x[1], 1.0)" There are many more possibilities, see help(SubDomain) help(DirichletBC) P. E. Farrell (Oxford) FEniCS I May 6, 2015 12 / 17 Step by step: defining the right-hand side The right-hand side f = −6 may be defined as follows: f = Expression("-6") or (more efficiently) as f = Constant(-6.0) Using a Constant means that the intermediate C++ code won’t be regenerated when the value changes. P. E. Farrell (Oxford) FEniCS I May 6, 2015 13 / 17 Step by step: defining variational problems Variational problems are defined in terms of solution and test functions: u = Function(V) v = TestFunction(V) We now have all the objects we need in order to specify the form: F = inner(grad(u), grad(v))*dx - f*v*dx Here dx is a type of class Measure that means ”integrate over the whole volume”. There are other measures: for example, ds means ”integrate over exterior facets”. P. E. Farrell (Oxford) FEniCS I May 6, 2015 14 / 17 Step by step: solving variational problems Once a variational problem has been defined, it may be solved by calling the solve function: solve(F == 0, u, bc) Nice! P. E. Farrell (Oxford) FEniCS I May 6, 2015 15 / 17 Step by step: post-processing The solution and the mesh may be plotted by calling: plot(u, interactive=True) The interactive=True argument is necessary for the plot to remain on the screen and allows the plots to be rotated, translated and zoomed. For postprocessing in ParaView, store the solution in XDMF format: file = File("poisson.xdmf") file << u P. E. Farrell (Oxford) FEniCS I May 6, 2015 16 / 17 FEniCS 01 Challenge! I Run the code and look at the solution. I Change the domain to [0, 1]3 . (Choose a new solution and fix the source term/BC to suit.) I Try using quadratic Lagrange elements. I A crucial step in developing solvers is verifying their implementation. I Wrap the 2D linear Poisson solver in a function that takes in the mesh size N and returns the L2 and H 1 errors. (Hint: help(errornorm)). I Solve the problem for N=10, 20, 40 and store the errors in a list. I Verify that the approximation is converging at the expected rate. I What is the scaling of the method (time in seconds vs number of degrees of freedom)? How would you make an O(n) method? P. E. Farrell (Oxford) FEniCS I May 6, 2015 17 / 17 Implementing finite element models in Python/FEniCS: time-dependent linear PDEs Patrick Farrell, Hans Petter Langtangen, Anders Logg, Marie Rognes MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) FEniCS II May 6, 2015 1 / 10 The heat equation We will solve the simplest extension of the Poisson problem into the time domain, the heat equation: ∂u − ∆u = f in Ω for t > 0 ∂t u = g on ∂Ω for t > 0 u = u0 in Ω at t = 0 The solution u = u(x, t), the right-hand side f = f (x, t) and the boundary value g = g(x, t) may vary in space (x = (x0 , x1 , ...)) and time (t). The initial value u0 is a function of space only. P. E. Farrell (Oxford) FEniCS II May 6, 2015 2 / 10 Time-discretization of the heat equation We discretize in time using the backward Euler method: ∂u n un − un−1 (t ) ≈ , u(tn ) ≈ un , ∂t ∆t Semi-discretization of the heat equation: f n = f (tn ) un − un−1 − ∆un = f n ∆t Algorithm I Start with u0 and choose a timestep ∆t > 0. I For n = 1, 2, . . ., solve for un : un − ∆t∆un = un−1 + ∆tf n P. E. Farrell (Oxford) FEniCS II May 6, 2015 3 / 10 Variational problem for the heat equation Find un ∈ V n such that a(un , v) = Ln (v) for all v ∈ V̂ where Z uv + ∆t∇u · ∇v dx a(u, v) = Ln (v) = ZΩ un−1 v + ∆tf n v dx Ω Note that the bilinear form a(u, v) is constant in time while the linear form Ln depends on time. P. E. Farrell (Oxford) FEniCS II May 6, 2015 4 / 10 Test problem As in the steady case, we construct a test problem for which we can easily check the answer. We first define the exact solution by u = 1 + x2 + αy 2 + βt We insert this into the heat equation: f = u̇ − ∆u = β − 2 − 2α The initial condition is u0 = 1 + x2 + αy 2 This technique is called the method of manufactured solutions. P. E. Farrell (Oxford) FEniCS II May 6, 2015 5 / 10 Handling time-dependent expressions We need to define a time-dependent expression for the boundary value: alpha = 3 beta = 1.2 g = Expression("1 + x[0]*x[0] + alpha*x[1]*x[1] + beta*t", alpha=alpha, beta=beta, t=0) Updating parameter values: g.t = t P. E. Farrell (Oxford) FEniCS II May 6, 2015 6 / 10 Projection and interpolation We need to project the initial value into Vh : u0 = project(g, V) We can also interpolate the initial value into Vh : u0 = interpolate(g, V) P. E. Farrell (Oxford) FEniCS II May 6, 2015 7 / 10 Implementation from dolfin import * # Mesh and function space mesh = UnitSquareMesh(32, 32) V = FunctionSpace(mesh, "Lagrange", 1) # Time variables dt = Constant(0.3); t = float(dt); T = 1.8 g_expr = "1 + x[0]*x[0] + alpha*x[1]*x[1] + beta*t" g = Expression(g_expr, alpha=3.0, beta=1.2, t=0, degree=2) # Previous and current solution u0 = interpolate(g, V); u1 = Function(V) # Variational problem at each time u = TrialFunction(V) v = TestFunction(V) f = Constant(1.2 - 2. - 2*3.0) a = u*v*dx + dt*inner(grad(u), grad(v))*dx L = u0*v*dx + dt*f*v*dx bc = DirichletBC(V, g, "on_boundary") while t <= T: g.t = t solve(a == L, u1, bc) u0.assign(u1) t += float(dt) plot(u1, interactive=True) P. E. Farrell (Oxford) FEniCS II May 6, 2015 8 / 10 Remark on efficiency The previous code will reassemble the mass and stiffness matrices every timestep. Where efficiency is a concern, you can call assemble to assemble matrices outside of the time loop. This makes your code faster, but uglier too: A = assemble(a) # A is a Matrix b = assemble(L) # b is a Vector solve(A, u.vector(), b) P. E. Farrell (Oxford) FEniCS II May 6, 2015 9 / 10 FEniCS 02 Challenge! I Run the code and look at the solution. (Try moving the plot inside the timeloop.) I Change the chosen solution such that f is time-dependent. Make sure to update anything necessary in the time loop. I Change the code to use forward Euler. What happens? I Change the code to implement the θ-method, and compare the results. P. E. Farrell (Oxford) FEniCS II May 6, 2015 10 / 10 Implementing finite element models in Python/FEniCS: steady nonlinear PDEs Patrick Farrell, Hans Petter Langtangen, Marie Rognes, Garth Wells MMSC: Python in Scientific Computing May 6, 2015 P. E. Farrell (Oxford) FEniCS III May 6, 2015 1/7 Nonlinear problems Consider a Poisson-type equation where the diffusivity depends on the solution itself: −∇ · (γ(u)∇u) = f in Ω u=g on ∂Ω where γ(u) = (2 + 1 |∇u|2 )(p−2)/2 2 This particular choice of γ defines the p-Laplace equation. P. E. Farrell (Oxford) FEniCS III May 6, 2015 2/7 Canonical nonlinear variational problem The following canonical notation is used for (possibly) nonlinear problems: Nonlinear variational problem Find u ∈ V such that F(u; v) = 0 for all v ∈ V̂ . F is a rank-one form: test function, no trial function. When assembled at a given u, F yields a vector. Sooner or later your code always becomes nonlinear. P. E. Farrell (Oxford) FEniCS III May 6, 2015 3/7 Variational formulation Multiplying with a test function and integrating by parts yields Z Z F(u; v) = ∇v · γ(u)∇u dx − f v dx. Ω Ω In UFL, this is written as def gamma(u): return (epsilon**2 + 0.5 * inner(grad(u), grad(u)))**((p-2)/2) F = inner(grad(v), gamma(u) * grad(u))*dx - inner(f, v)*dx P. E. Farrell (Oxford) FEniCS III May 6, 2015 4/7 Newton’s method (Newton-Kantorovitch) Default for solving nonlinear problems: Newton’s method. Newton’s method in function spaces consists of the iteration: uk+1 = uk − J (uk )−1 F(uk ), where J (uk ) is the Fréchet derivative of the nonlinear operator at uk . Usually, coding the Jacobian is a major headache. In FEniCS, it is not: J = derivative(F, u) This is done for you automatically inside the solve. P. E. Farrell (Oxford) FEniCS III May 6, 2015 5/7 p-Laplace in FEniCS: implementation from dolfin import * mesh = UnitSquareMesh(32, 32) V = FunctionSpace(mesh, "Lagrange", 1) u = interpolate(Expression("x[0]*(1-x[0])*x[1]*(1-x[1])"), V) v = TestFunction(V) f = Constant(1.0); g = Constant(0.0) epsilon = Constant(1.0e-5); p = Constant(3) bc = DirichletBC(V, g, DomainBoundary()) def gamma(u): return (epsilon**2 + 0.5 * inner(grad(u), grad(u)))**((p-2)/2) F = inner(grad(v), gamma(u) * grad(u))*dx - inner(f, v)*dx solve(F == 0, u, bc) plot(u, interactive=True) P. E. Farrell (Oxford) FEniCS III May 6, 2015 6/7 FEniCS 03 Challenge! I Starting from a good initial guess is crucial for nonlinear problems. I What happens if a zero initial guess is used? I Try increasing the maximum number of iterations allowed: solve(F == 0, u, bc, solver_parameters={"newton_solver": {"maximum_iterations": 100}}) I Change p to 5. What happens now? I A common technique for solving nonlinear problems is continuation: I parameterise the problem with a parameter (e.g. p) I start with a simple problem (e.g. p = 2) I use that answer as the initial guess for the next problem (e.g. p ← p + 1) I repeat until you’ve solved the problem of interest. I Starting from a zero initial guess, can you use continuation to solve p = 5 in fewer linear solves than tackling it directly? P. E. Farrell (Oxford) FEniCS III May 6, 2015 7/7 Implementing finite element models in Python/FEniCS: coupled PDEs Patrick Farrell, Marie Rognes, Garth Wells MMSC: Python in Scientific Computing June 10, 2015 P. E. Farrell (Oxford) FEniCS IV June 10, 2015 1 / 10 P. E. Farrell (Oxford) FEniCS IV June 10, 2015 2 / 10 George Stokes I Born in Sligo, Ireland I Lucasian Professor of Mathematics, Cambridge, 1849–1903 I Member of Parliament, 1887–1892 I President of the Royal Society, 1885–1890 I Fundamental contributions to incompressible flow, optics, geodesy, spectroscopy, . . . P. E. Farrell (Oxford) FEniCS IV June 10, 2015 3 / 10 The Stokes equations We consider the stationary Stokes equations: find the velocity u and the pressure p such that −∇ · (2ν(u) − p I) = f in Ω ∇ · u = 0 in Ω where (u) = 1 2 ∇u + (∇u)T and with boundary conditions u = 0 on ∂ΩD −(2ν − p I) · n = p0 n on ∂ΩN If viscosity ν varies with u (or p), ν = ν(u) this is a nonlinear system of partial differential equations. P. E. Farrell (Oxford) FEniCS IV June 10, 2015 4 / 10 The Stokes equations: variational formulation Assume that u ∈ V and p ∈ Q, then w = (u, p) ∈ V × Q = W . Let f = 0. Step 1 Multiply by test functions (v, q) ∈ W and integrate first equation by parts Z Z Z 2ν(u) · ∇v dx− p∇ · v dx − (2ν(u) − p I) · n · v ds = 0 Ω ∂Ω ZΩ ∇ · u q dx = 0 Ω Step 2 Adding the equations and incorporating the boundary conditions we obtain: find (u, p) ∈ W = V0 × Q such that Z Z Z Z 2ν(u) · ∇v dx − p∇ · v dx − ∇ · u q dx + p0 v · n ds = 0 Ω Ω Ω ∂ΩN for all (v, q) ∈ W = V0 × Q where V0 = {v ∈ V such that v|∂ΩD = 0}. P. E. Farrell (Oxford) FEniCS IV June 10, 2015 5 / 10 Step by step: creating mixed function spaces Mixed function spaces are created by taking the product of simpler spaces: V Q W # = = = W VectorFunctionSpace(mesh, "CG", 2) FunctionSpace(mesh, "CG", 1) MixedFunctionSpace([V, Q]) = V * Q You can define functions on mixed spaces and split into components: w = Function(W) (u, p) = split(w) ... and arguments: y = TestFunction(W) (v, q) = split(y) Choice of mixed function space is crucial. P. E. Farrell (Oxford) FEniCS IV June 10, 2015 6 / 10 Step by step: defining a boundary condition on a subspace Assume that we have a mixed function space: V = VectorFunctionSpace(...) Q = FunctionSpace(...) W = MixedFunctionSpace([V, Q]) The subspaces of W can be retrieved using sub: W0 = W.sub(0) Note that W0 is not the same as V. The following code defines a homogenous Dirichlet (boundary) condition on the first subspace at the part where x0 = 0. g = (0.0, 0.0) bc = DirichletBC(W.sub(0), g, "near(x[0], 0.0)") P. E. Farrell (Oxford) FEniCS IV June 10, 2015 7 / 10 Stokes: defining the variational form Assume that we have w = Function(W) (u, p) = split(w) (v, q) = TestFunctions(W) p0 = ...; nu = ...; n = ... We can now specify the linear form F epsilon = sym(grad(u)) F = (2*nu*inner(epsilon, grad(v)) \ - div(u)*q - div(v)*p)*dx \ + p0*dot(v, n)*ds Recall that dx denotes integration over cells and ds denotes integration over exterior (boundary) facets. dS denotes integration over interior facets, but we won’t get to discontinuous Galerkin discretisations in this course. P. E. Farrell (Oxford) FEniCS IV June 10, 2015 8 / 10 Stokes implementation from dolfin import * # Define mesh and geometry mesh = Mesh("dolphin.xml") n = FacetNormal(mesh) # V Q W Define Taylor--Hood function space W = VectorFunctionSpace(mesh, "CG" , 2) = FunctionSpace(mesh , "CG", 1) = MixedFunctionSpace([V, Q]) # Define Function and TestFunction(s) w = Function(W); (u, p) = split(w) (v, q) = TestFunctions(W) # Define viscosity and bcs nu = Expression("0.2*(1+pow(x[1],2))", degree=2) p0 = Expression("1.0-x[0]", degree=1) bcs = DirichletBC(W.sub(0), (0.0, 0.0), "on_boundary && !(near(x[0], 0.0) || near(x[0], 1.0))") # Define variational form epsilon = sym(grad(u)) F = (2*nu*inner(epsilon, grad(v)) - div(u)*q - div(v)*p)*dx\ + p0*dot(v,n)*ds # Solve problem solve(F == 0, w, bcs) # Plot solutions plot(u, title="Velocity", interactive=True) plot(p, title="Pressure", interactive=True) P. E. Farrell (Oxford) FEniCS IV June 10, 2015 9 / 10 FEniCS 04 Challenge! Solve the Stokes problem on Ω defined by the dolphin.xml mesh, defined by the following data −∇ · (2ν(u) − p I) = 0 in Ω ∇ · u = 0 in Ω −(2ν(u) − p I) · n = p0 n on ∂ΩN = {(x0 , x1 )| x0 = 0 or x0 = 1} p 0 = 1 − x0 u = 0 on ∂ΩD = ∂Ω\∂ΩN I Consider a constant viscosity ν = 0.2, compute and plot the solutions. I Consider the nonlinear viscosity ν = ν(u) = 0.5(∇u · ∇u)1/(2(k−1)) , k = 4 Compute and plot the solutions. Hint: For nonlinear problems, it is often necessary to solve a simpler problem to generate an initial guess. P. E. Farrell (Oxford) FEniCS IV June 10, 2015 10 / 10 Implementing finite element models in Python/FEniCS: variational inequalities Patrick Farrell MMSC: Python in Scientific Computing June 10, 2015 P. E. Farrell (Oxford) FEniCS V June 10, 2015 1/5 Obstacle problems Suppose an elastic membrane is attached to a flat wire frame which encloses a region Ω of the plane. Suppose this membrane is subject to a distributed load f (x, y). Then the equilibrium position z = u(x, y) satisfies −∇2 u = f, u = 0 on ∂Ω. P. E. Farrell (Oxford) FEniCS V June 10, 2015 2/5 Obstacle problems Suppose an elastic membrane is attached to a flat wire frame which encloses a region Ω of the plane. Suppose this membrane is subject to a distributed load f (x, y). Then the equilibrium position z = u(x, y) satisfies −∇2 u = f, u = 0 on ∂Ω. Now suppose that an obstacle is placed underneath the membrane. The obstacle z = ψ(x, y) is continuous, differentiable and ψ|∂Ω ≤ 0. The task is to find a region R and solution u such that u coincides with ψ on R, and u satisfies the PDE on Ω \ R. P. E. Farrell (Oxford) FEniCS V June 10, 2015 2/5 Variational formulation Let Kψ = {v ∈ H01 (Ω) | v ≥ φ}. Then the variational formulation is a variational inequality: find u ∈ Kψ such that Z Z f (v − u) ∀ v ∈ Kψ . ∇u · ∇(v − u) ≥ Ω P. E. Farrell (Oxford) Ω FEniCS V June 10, 2015 3/5 Variational formulation Let Kψ = {v ∈ H01 (Ω) | v ≥ φ}. Then the variational formulation is a variational inequality: find u ∈ Kψ such that Z Z f (v − u) ∀ v ∈ Kψ . ∇u · ∇(v − u) ≥ Ω Ω Note that the scalar VI f 0 (u)(x − u) ≥ 0 ∀ x ∈ I = [l, u] is equivalent to: exactly one of the following conditions must hold: I If l < x < u, then f 0 (x) = 0; I If l = x, then f 0 (x) ≥ 0; I If x = u, then f 0 (x) ≤ 0. P. E. Farrell (Oxford) FEniCS V June 10, 2015 3/5 Useful FEniCS hints The code solve(F == 0, u, bc) is equivalent to problem = NonlinearVariationalProblem(F, u, bc, J=derivative(F, u)) solver = NonlinearVariationalSolver(problem) solver.solve() You can impose bounds on the solution with solver.parameters["nonlinear_solver"] = "snes" solver.parameters["snes_solver"]["linear_solver"] = "lu" solver.parameters["snes_solver"]["method"] = "vinewtonrsls" solver.solve(lower, upper) P. E. Farrell (Oxford) FEniCS V June 10, 2015 4/5 FEniCS 05 Challenge! Solve the Poisson obstacle problem with Ω = [−2, 2]2 , f = 0 and (p 1 − x2 − y 2 if x2 + y 2 < 1 ψ(x, y) = 0 otherwise. P. E. Farrell (Oxford) FEniCS V June 10, 2015 5/5 Implementing finite element models in Python/FEniCS: steady nonlinear vector-valued PDEs Patrick Farrell, Anders Logg, Marie Rognes MMSC: Python in Scientific Computing May 7, 2015 P. E. Farrell (Oxford) FEniCS VI May 7, 2015 1/8 Static hyperelasticity −∇ · P = B in Ω u=g on ΓD P ·n=T on ΓN I u is the displacement (vector-valued) I P = P (u) is the first Piola–Kirchhoff stress tensor I B is a given body force per unit volume I g is a given boundary displacement I T is a given boundary traction P. E. Farrell (Oxford) FEniCS VI May 7, 2015 2/8 Variational problem Multiply by a test function v ∈ V̂ and integrate by parts: Z Z Z P : ∇v dx − (P · n) · v ds − ∇ · (P · v) dx = Ω Ω ∂Ω Note that v = 0 on ΓD and P · n = T on ΓN Find u ∈ V such that Z Z Z P : ∇v dx = B · v dx + Ω Ω T · v ds ΓN for all v ∈ V̂ P. E. Farrell (Oxford) FEniCS VI May 7, 2015 3/8 Stress–strain relations I F = I + ∇u is the deformation gradient I C = F > F is the right Cauchy–Green tensor I E = 12 (C − I) is the Green–Lagrange strain tensor I W = W (E) is the strain energy density I Sij = ∂W ∂Eij is the second Piola–Kirchhoff stress tensor I P = F S is the first Piola–Kirchhoff stress tensor St. Venant–Kirchhoff strain energy function: W (E) = P. E. Farrell (Oxford) λ (tr(E))2 + µ tr(E 2 ) 2 FEniCS VI May 7, 2015 4/8 Useful FEniCS tools (I) Loading a mesh from a file: mesh = Mesh("whatever.xml") Vector-valued function spaces: V = VectorFunctionSpace(mesh, "Lagrange", 1) Vector-valued Constants: g = Constant((0, 0, -9.81)) # accel. due to gravity P. E. Farrell (Oxford) FEniCS VI May 7, 2015 5/8 Useful FEniCS tools (II) Defining subdomains/boundaries: class MyBoundary(SubDomain): def inside(self, x, on_boundary): return on_boundary and near(x[0], 0.0) Marking boundaries: my_boundary_1 = MyBoundary1() my_boundary_2 = MyBoundary2() boundaries = FacetFunction("uint", mesh) boundaries.set_all(0) my_boundary_1.mark(boundaries, 1) my_boundary_2.mark(boundaries, 2) ds = ds[boundaries] F = ...*ds(0) + ...*ds(1) P. E. Farrell (Oxford) FEniCS VI May 7, 2015 6/8 Useful FEniCS tools (III) Computing derivatives of expressions: I = F = C = ... E = W = S = P = Identity(3) I + grad(u) F.T * F variable(...) ... diff(W, E) F*S Computing functionals of a solution: J = assemble(u[0]*dx) / assemble(Constant(1)*dx, mesh=mesh) P. E. Farrell (Oxford) FEniCS VI May 7, 2015 7/8 FEniCS 06 Challenge! Compute the deflection of a regular 10 × 2 LEGO brick. Use the St. Venant–Kirchhoff model and assume that the LEGO brick is made of PVC plastic. The LEGO brick is subject to gravity of size g = −9.81 m/s2 and a downward traction of size 5000 N/m2 at its end point. g = −9.81m/s2 T = 5000 N/m2 Compute the average value of the displacement in the z-direction. Mesh and material parameters: http://www.maths.ox.ac.uk/courses/course/26423 Submit your answers (one real number) to [email protected] . P. E. Farrell (Oxford) FEniCS VI May 7, 2015 8/8 Implementing finite element models in Python/FEniCS: Navier-Stokes Patrick Farrell, Anders Logg, André Massing MMSC: Python in Scientific Computing May 7, 2015 P. E. Farrell (Oxford) FEniCS VII May 7, 2015 1 / 11 The incompressible Navier–Stokes equations ρ(u̇ + u · ∇u) − ∇ · σ(u, p) = f in Ω × (0, T ] ∇·u=0 in Ω × (0, T ] u = gD on ΓD × (0, T ] σ · n = gN on ΓN × (0, T ] u(·, 0) = u0 I I I I I I I I in Ω u is the fluid velocity and p is the pressure ρ is the fluid density σ(u, p) = 2µ(u) − pI is the Cauchy stress tensor (u) = 12 (∇u + (∇u)> ) is the symmetric gradient f is a given body force per unit volume gD is a given boundary displacement gN is a given boundary traction u0 is a given initial velocity P. E. Farrell (Oxford) FEniCS VII May 7, 2015 2 / 11 Variational problem Multiply the momentum equation by a test function v and integrate by parts: Z Z Z Z ρ(u̇ + u · ∇u) · v dx + σ(u, p) : (v) dx = f · v dx + gN · v ds Ω Ω Ω ΓN Short-hand notation: hρu̇, vi + hρu · ∇u, vi + hσ(u, p), (v)i = hf, vi + hgN , viΓN Multiply the continuity equation by a test function q and sum up: find (u, p) ∈ V such that hρu̇, vi + hρu · ∇u, vi + hσ(u, p), (v)i + h∇ · u, qi = hf, vi + hgN , viΓN for all (v, q) ∈ V̂ P. E. Farrell (Oxford) FEniCS VII May 7, 2015 3 / 11 Discrete variational problem Time-discretization leads to a saddle-point problem on each time step: M + ∆tA + ∆tN (U ) ∆tB U b = P 0 ∆tB > 0 I Efficient solution of the saddle-point problem relies on the efficiency of special-purpose preconditioners (Uzawa iteration, Schur complement preconditioners, . . . ). I We will use a splitting approach (more complicated assembly, easier linear systems). P. E. Farrell (Oxford) FEniCS VII May 7, 2015 4 / 11 The classical Chorin-Temam projection method I Step 1: Compute tentative velocity uF solving uF − un − ν∆uF + (u∗ · ∇)u∗∗ = f n+1 ∆t uF = gD ∂uF =0 ∂n in Ω on ΩD on ΩN I Step 2: Compute a corrected velocity un+1 and a new pressure pn+1 solving un+1 − uF + ∇pn+1 = 0 in Ω ∆t ∇ · un+1 = 0 in Ω un+1 · n = 0 P. E. Farrell (Oxford) FEniCS VII on ∂Ω May 7, 2015 5 / 11 Computing the tentative velocity In principle, the term (u∗ · ∇)u∗∗ can be approximated in several ways I Explicit: u∗ = u∗∗ = un ⇒ diffusion-reaction equation I Semi-implicit u∗ = un and u∗∗ = un+1 ⇒ advection-diffusion-reaction equation I Fully-implicit u∗ = u∗∗ = un+1 retaining the basic non-linearity in the Navier-Stokes equations The natural outflow condition ν∂n u − pn = 0 is artificially enforced by requiring I ∂n uF = 0 on ∂ΩN in step 1 I pn+1 = 0 on ∂ΩN in step 2 P. E. Farrell (Oxford) FEniCS VII May 7, 2015 6 / 11 Solving the projection step un+1 − uF + ∇pn+1 = 0 and using requirement ∆t = 0 yields Applying ∇· to ∇ · un+1 ∆pn+1 = 1 ∇ · uF ∆t in Ω We already required p = 0 on ∂ΩN Multiplying un+1 − ∆t uF + ∇pn+1 = 0 with n and restricting to ∂ΩD gives ∂pn+1 = 0 on ∂ΩD ∂n Compute un+1 by un+1 = uF − ∆t∇pn+1 including boundary conditions for u at t = tn+1 P. E. Farrell (Oxford) FEniCS VII May 7, 2015 7 / 11 Chorin-Teman projection method – Summary (1) Compute tentative velocity uF by ( uF − un , v) + ((u∗ · ∇)u∗∗ , v) + ν(∇uF , ∇v) − (f, v) = 0 ∆t including boundary conditions for the velocity. (2) Compute new pressure pn+1 by (∇pn+1 , ∇q) + 1 (∇ · uF , q) = 0 ∆t including boundary conditions for the pressure. (3) Compute corrected velocity by (un+1 − uF , v) + ∆t(∇pn+1 , v) = 0 including boundary conditions for the velocity. P. E. Farrell (Oxford) FEniCS VII May 7, 2015 8 / 11 Useful FEniCS tools Advection term: F = inner(v, grad(u)*u)*dx + ... P. E. Farrell (Oxford) FEniCS VII May 7, 2015 9 / 11 Useful FEniCS tools Apply C++ optimisation flags to generated assembly code: parameters["form_compiler"]["cpp_optimize"] = True parameters["form_compiler"]["cpp_optimize_flags"] = \ "-O3 -ffast-math -march=native" Running in parallel: $ mpiexec -n 4 python navier_stokes.py P. E. Farrell (Oxford) FEniCS VII May 7, 2015 10 / 11 FEniCS 07 Challenge! Solve the time-dependent Navier-Stokes equation on the domain Ω defined by cylinder.xml. Data: ν = 0.001 m2 /s, ρ = 1.0 kg/m3 , T = 8 s, ∆t = 0.001 s, f = 0. Boundary conditions: I Inflow on the left given by U = 4Um y(H − y) sin(πt/8)/H 2 , V = 0. with Um = 1.5 m/s, H = ??? m. I Outflow on the right. I No-slip elsewhere. This is the famous benchmark of Schäfer and Turek (1996). P. E. Farrell (Oxford) FEniCS VII May 7, 2015 11 / 11 Implementing finite element models in Python/FEniCS: optimal control Patrick Farrell, Simon Funke MMSC: Python in Scientific Computing June 10, 2015 P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 1 / 11 What is PDE-constrained optimisation? Optimisation problems where at least one constraint is a partial differential equation. Applications I Shape and topology optimisation (e.g. optimal shape of a wing) I Data assimilation (e.g. weather prediction) I Inverse problems (e.g. petroleum exploration) I ... P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 2 / 11 Hello World of PDE-constrained optimisation We will solve an optimisation problem involving the Poisson equation: 1 min u,m 2 subject to Z α (u − ud ) dx + 2 Ω −∆u = m u = u0 2 Z m2 dx Ω in Ω on ∂Ω This problem can be physically interpreted as: find the heating/cooling term m for which u best approximates the desired heat distribution ud . The regularisation term in the functional ensures existence and uniqueness for α > 0. P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 3 / 11 The canonical abstract form min J (u, m) u,m subject to: F(u, m) = 0, with I the objective functional J . I the parameter m. I the PDE operator F with solution u ∈ U, parametrised by m ∈ M. P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 4 / 11 Oneshot solution strategy We form the Lagrangian L: L(u, λ, m) = J (u, m) + λ∗ F (u, m) Optimality conditions (Karush-Kuhn-Tucker): ∇L = 0 at an optimum: dL = 0, du dL = 0, dλ dL = 0. dm Oneshot approach: solve these three (coupled, often nonlinear) PDEs together. P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 5 / 11 Comments on oneshot approach The oneshot approach can be extremely fast, but very difficult to converge. (Very difficult to ensure convergence of Newton’s method, and to solve the resulting linear systems.) Oneshot approaches tend to work best for steady PDEs, reduced approaches tend to work best for time-dependent PDEs. (Reduced approaches are outside the scope of the course.) P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 6 / 11 Forming the Lagrangian L(u, λ, m) = 1 2 Z Ω (u − ud )2 dx + α 2 Z m2 dx + Ω V = Z = z = (u, FunctionSpace(mesh, "Lagrange", 1) MixedFunctionSpace([V, V, V]) Function(Z) lmbd, m) = split(z) L = + + - 0.5*inner(u-ud, u-ud)*dx 0.5*alpha*inner(m, m)*dx inner(grad(u), grad(lmbda))*dx m*lmbda*dx P. E. Farrell (Oxford) FEniCS VIII Z ∇λ · ∇u − λm dx Ω June 10, 2015 7 / 11 KKT conditions −∇2 u = m −∇2 λ = J 0 αm = λ kkt = derivative(L, z, TestFunction(Z)) P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 8 / 11 Implementation from dolfin import * # Define discrete Functionspace mesh = UnitSquareMesh(100, 100) U = FunctionSpace(mesh, "CG", 1) # Space for forward solution V = FunctionSpace(mesh, "CG", 1) # Space for adjoint solution M = FunctionSpace(mesh, "DG", 0) # Space for control Z = MixedFunctionSpace([U, V, M]) # Define Functions z = Function(Z) (u, lmbda, m) = split(z) # Define variational problem F = inner(grad(u), grad(lmbda))*dx - m*lmbda*dx # Define functional ud = Expression("sin(pi*x[0])*sin(pi*x[1])") # Desired temperature profile J = inner(u-ud, u-ud)*dx + Constant(1e-6)*inner(m, m)*dx # Define boundary conditions bc_u = DirichletBC(Z.sub(0), 0.0, "on_boundary") bc_lmbd = DirichletBC(Z.sub(1), 0.0, "on_boundary") bcs = [bc_u, bc_lmbd] # Derive optimality conditions L = J + F kkt = derivative(L, z, TestFunction(Z)) # Solve Poisson problem solve(kkt == 0, z, bcs) P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 9 / 11 FEniCS 08 Challenge! In http://dx.doi.org/10.1137/S0363012994261707, Ito and Kunisch consider the following optimal control problem: α 1 ku − ud k2L2 (Ω) + kmk2L2 (Γ) 2 2 2 3 subject to −∇ u + u − u = 0 on Ω, min u,m ∇u · n = m on Γ. This Ginzburg-Landau PDE arises in superconductivity. Solve this optimal control problem with Ω = [0, 1] × [0, 2], α = 10−7 , ud = 3. Hint: we can’t represent functions on trace spaces yet (i.e. only on the boundary), so your m needs to live in a volume function space. Modify the regularisation term appropriately. P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 10 / 11 Final Challenge! MMSC: for this to count for your MSc, you have to write a final report. Your task: I Implement a finite element discretisation of a PDE. I Take a discretisation and configuration from the literature, or invent your own. I Bonus points for: nonlinearity, coupling, mathematical interest, . . . I Include an MMS verification. I Submit working code and a report (≤ 20 pages) discussing the PDE and its implementation. I Inspiration: your summer projects; the PDE coffee table book. I Deadline: 6 July 2015. P. E. Farrell (Oxford) FEniCS VIII June 10, 2015 11 / 11