Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial...

20
Wuppertal 2018 Python for Scientific Computing . FOTIOS KASOLIS . Python NumPy SciPy MatplotlibFEniCS

Transcript of Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial...

Page 1: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

Wuppertal 2018

Python for Scientific Computing . F O T I O S K A S O L I S .

Python ● NumPy ● SciPy ● Matplotlib● FEniCS

Page 2: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

1

Python for Scientific Computing a tutorial introduction

FOTIOS KASOLIS. Version 15/05/2018

§1. The fundamentals of Python

Simple arithmetic operations

Python is a general-purpose programming language. The built-in data types in Python are

scalars, strings, lists, tuples, dictionaries, and sets. To do scalar arithmetic as we would do with

a common calculator, we use + for addition, – for subtraction, * for multiplication, / for divi-

sion, and ** for exponentiation. Further, to associate a value with a symbolic name, we use

the assignment operator =, while the separation marker . is used to separate the integer part

of a number from its fractional part. Python distinguishes between integers and floating point

numbers and thus, we have to explicitly enforce a floating point representation by using the

separation marker or float, as depicted in the example below;

In [1]: x, y = 1/2, 1.0/2

In [2]: x, y

Out[2]: (0, 0.5)

In [3]: type(x), type(y)

Out[3]: (int, float)

where the difference between integer and floating point numbers becomes apparent, when

using type to obtain the type of the objects. Further, it is possible to enter a complex number

𝑥 + i𝑦, where 𝑥, 𝑦 ∈ ℝ and i2 = −1, as shown below.

In [4]: z, w = 3 + 4j, complex(3,-4)

In [5]: z*w

Out[5]: (25+0j)

Symbolic names or identifiers, such as x, y, z and w in the examples above, can start with upper-

or lowercase letters, or with an underscore, followed by none or more letters, underscores

and numerical digits. Python is case sensitive, which means that fun and Fun are distinct iden-

tifiers.

In [6]: peanut_butter = 1

In [7]: _9 = 3 # not a nice name for a variable

Page 3: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

Here, the hash character # signals a single-line comment. Further, we can access the docstrings

with help, autocomplete with the tab key , and look for previously entered user input with

the up-arrow key ↑, as shown below, when using the IPython console.

In [8]: import math

In [9]: math.s

math.sin math.sinh math.sqrt

In [9]: help(math.sin)

Help on built-in function sin in module math:

sin(...)

sin(x)

Return the sine of x (measured in radians). Q

In the last example, we use import to import the math module; after requesting and reading the

built-in information for the math.sin function, we return to the prompt by hitting the key Q.

Some simple computations follow.

In [10]: math.pi

Out[10]: 3.141592653589793

In [11]: 4.0 * math.atan(1)

Out[11]: 3.141592653589793

In [12]: d = math.sqrt(3.**2 + 4.**2)

In [13]: d

Out[13]: 5.0

In [14]: math.log(math.exp(1000))

---------------------------------------------------------------------------

OverflowError Traceback (most recent call last)

<ipython-input-22-30dfb1a2d2e6> in <module>()

----> 1 math.log(math.exp(1000))

OverflowError: math range error

Ordered data containers: strings, lists, and tuples

Here, we shortly present strings, lists, and tuples. These data types are ordered, which means

that each element has a position index, starting at zero, and hence, particular parts of these

containers can be extracted using their identifiers followed by the position index enclosed by

square brackets [ ], and possibly in conjunction with the slicing operator :. When slicing,

Python uses left closed – right open ranges; that is, 1:4 refers to positions 1, 2, and 3. Further,

Python allows backward indexing and slicing, with the index of the last element of an ordered

data container being -1.

Data container with n elements

Forward position index 0 1 2 3 4 . n-1

Backward position index -n . . . . . -1

Page 4: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

3

● Strings are ordered sequences of symbols that are enclosed by single or double quotes;

that is, ' ' or " ", respectively. Strings are immutable, meaning that they cannot be

modified. In the example below, type s. to find out functions that perform string op-

erations.

In [1]: s = 'Red rum, sir, is murder'

In [2]: s[0], s[1], s[2], s[-1], s[-2], s[-3]

Out[2]: ('R', 'e', 'd', 'r', 'e', 'd')

In [3]: s[0:3], s[:3], s[-3:]

Out[3]: ('Red', 'Red', 'der')

● Lists are ordered sequences of data of arbitrary type that are enclosed by square brackets

[ ]. Lists are mutable, meaning that items can be added, removed, and replaced.

In [4]: l = [2, 'b', ['or', 'not']]

In [5]: l[0], l[1], l[2][0], l[2][1], l[0], l[1]

Out[5]: (2, 'b', 'or', 'not', 2, 'b')

In [6]: len(l)

Out[6]: 3

In [7]: l.

l.append l.extend l.insert l.remove l.sort

l.count l.index l.pop l.reverse

● Tuples are ordered sequences of arbitrary data that are enclosed by round brackets ( ).

Tuples are immutable and hence, no changes are allowed.

In [7]: t = ('1', 2, ['3', 4, '5'])

In [8]: print(t[2][0]) # prints on screen

3

In [9]: t[0] = 0

---------------------------------------------------------------------------

TypeError Traceback (most recent call last)

<ipython-input-137-6e5fc4c1fe4b> in <module>()

----> 1 t[0] = 0

TypeError: 'tuple' object does not support item assignment

§2. NumPy array objects

Introducing the ndarray data type

For tasks that involve numerical matrix computations, the third party data type numpy.ndarray

provides ordered, homogeneous, and efficient multidimensional arrays. To access the ndarray

data type, we enter at IPython’s prompt import numpy as np . Then, all functionalities pro-

vided by NumPy can be found by np. . A NumPy array can be created from a list as shown

below, while the standard Python indexing and slicing rules apply.

Page 5: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

In [1]: a = np.array([1.0, 2, 0, 3, 4])

In [2]: type(a)

Out[2]: numpy.ndarray

In [3]: a[:3]

Out[3]: array([ 1., 2., 0.])

In [4]: a[-1]

Out[4]: 4.0

NumPy provides several array manipulation methods; for instance, we can retrieve the di-

mension, the number of elements, the number of dimensions (also called axes), the maximum

value, and the sum of the elements of an array using the following statements.

In [5]: a.shape # dimension

Out[5]: (5,)

In [6]: a.size # number of elements a.shape[0]*a.shape[1]

Out[6]: 5

In [7]: a.ndim # number of axes/dimensions len(a.shape)

Out[7]: 1

In [8]: a.max() # maximum value

Out[8]: 4.0

In [9]: a.sum() # sum

Out[9]: 10.0

Matrices can be created from lists of lists, while the standard Python indexing and slicing rules

apply. For instance,

In [10]: A = np.array([[1.0, 2], [0, 3], [4, 0]])

In [11]: A

Out[11]:

array([[ 1., 2.],

[ 0., 3.],

[ 4., 0.]])

In [12]: A[0,0]

Out[12]: 1.0

In [13]: A[0,1]

Out[13]: 2.0

In [14]: A[:,0]

Out[14]: array([ 1., 0., 4.])

NumPy uses row major storing order, which means that contiguous elements in a row are

stored next to each other in the physical (linear) memory. The access pattern on an array

affects the performance significantly, since contiguous elements can be accessed faster, due to

the physical proximity. As an example, the matrix A defined in In[10] is stored as

Physical memory . 1.0 2.0 . 0.0 3.0 . 4.0 0.0 .

The row major order affects several array manipulations and hence, some care is necessary, if

the user is familiar with languages that use column major storing order, such as GNU Octave

and Fortran.

Page 6: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

5

In [15]: A.flatten()

Out[15]: array([ 1., 2., 0., 3., 4., 0.])

In [16]: A.reshape(2, 3)

Out[16]:

array([[ 1., 2., 0.],

[ 3., 4., 0.]])

When senseful, NumPy allows the user to define the axis along which an operation is per-

formed, as depicted in the following code snippet.

In [17]: A

Out[17]:

array([[ 1., 2.],

[ 0., 3.],

[ 4., 0.]])

In [18]: A.sum() # sum of all elements

Out[18]: 10.0

In [19]: A.sum(axis = 0) # sum of the elements of each column

Out[19]: array([ 5., 5.])

In [20]: A.sum(axis = 1) # sum of the elements of each row

Out[20]: array([ 3., 3., 4.])

In [21]: A.max() # maximum value of all elements

Out[21]: 4.0

In [22]: A.max(axis = 0) # maximum value of each column

Out[22]: array([ 4., 3.])

In [23]: A.max(axis = 1) # maximum value of each row

Out[23]: array([ 2., 3., 4.])

Linear algebra

By default, all operations between NumPy arrays are performed element-by-element; for in-

stance,

In [1]: A = np.random.randint(10, size = 16).reshape(4, 4)

In [2]: A

Out[2]:

array([[3, 7, 4, 8],

[6, 7, 2, 8],

[9, 2, 2, 5],

[6, 9, 8, 8]])

In [3]: B = np.random.randint(10, size = 16).reshape(4, 4)

In [4]: B

Out[4]:

array([[5, 6, 0, 3],

[6, 5, 2, 2],

[4, 6, 9, 7],

[5, 8, 4, 6]])

In [5]: A*B

Out[5]:

array([[15, 42, 0, 24],

[36, 35, 4, 16],

[36, 12, 18, 35],

[30, 72, 32, 48]])

Page 7: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

whereas matrix multiplication is performed with dot, matmul, or in Python 3, also with the

operator @;

In [6]: np.matmul(A, B)

Out[6]:

array([[113, 141, 82, 99],

[120, 147, 64, 94],

[ 90, 116, 42, 75],

[156, 193, 122, 140]])

NumPy supports a set of linear algebra routines, which can be found in numpy.linalg;

In [7]: np.linalg.

np.linalg.LinAlgError np.linalg.eigvalsh np.linalg.pinv

np.linalg.absolute_import np.linalg.info np.linalg.print_function

np.linalg.bench np.linalg.inv np.linalg.qr

np.linalg.cholesky np.linalg.lapack_lite np.linalg.slogdet

np.linalg.cond np.linalg.linalg np.linalg.solve

np.linalg.det np.linalg.lstsq np.linalg.svd

np.linalg.division np.linalg.matrix_power np.linalg.tensorinv

np.linalg.eig np.linalg.matrix_rank np.linalg.tensorsolve

np.linalg.eigh np.linalg.multi_dot np.linalg.test

np.linalg.eigvals np.linalg.norm

For instance, the singular values of a matrix 𝐀 ∈ ℝ5×5 can be computed as depicted below.

In [7]: A = np.arange(25).reshape(5, 5)

In [8]: s = np.linalg.svd(A, compute_uv = False)

In [9]: print(s)

[ 6.99085940e+01 3.57609824e+00 6.39273548e-15 1.24087941e-15

2.39254844e-16]

SciPy provides a bigger set of optimized linear algebra routines under scipy.linalg, which are

the recommended routines. For instance, the following code snippet shows how to solve the

linear system 𝐀𝒙 = 𝒃, where 𝐀 ∈ ℝ10×10.

In [10]: from scipy import linalg

In [11]: A = A = np.random.rand(10, 10)

In [12]: b = np.ones(10)

In [13]: x = linalg.solve(A, b)

In [14]: x

Out[14]:

array([ 1.71750424, 3.2303496 , -0.73462578, -3.93415278, 9.90822961,

-3.38400644, -4.27047453, 5.47057225, -0.36802176, -5.54578902])

NumPy and SciPy provide a complete data processing environment, which, complemented

with Matplotlib visualization facilities, offers an integrated scientific platform.

Page 8: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

7

§3. Basic graphs with Matplotlib

The basic plotting function offered by matplotlib.pyplot is plot. To plot the graph of a func-

tion 𝑓: 𝑋 → 𝑌 we define a discrete version 𝑋ℎ of the domain 𝑋 using linspace , see In[5] below,

and we compute the function values at 𝑋ℎ; for instance, see In[6]. If we want to display the

generated figure on the screen, we call show.

In [1]: import numpy as np

In [2]: import matplotlib.pyplot as plt

In [3]: plt.rc('text', usetex = True)

In [4]: plt.rc('font', family = 'serif', size = 16)

In [5]: t = np.linspace(0.0, 5.0, 100)

In [6]: y = np.cos(2 * np.pi * t) * np.exp(-t)

In [7]: plt.plot(t, y, '-ok', color = [0.92549, 0, 0.54902])

In [8]: plt.xlabel(r'$\mathrm{time~(s)}$')

In [9]: plt.ylabel(r'$\mathrm{voltage~(mV)}$')

In [10]: plt.title(r'$\mathrm{The~function~with~values~}$'

r'$f(t) = \mathrm{e}^{-t}\cos{t}$')

In [11]: plt.grid(True)

In [12]: plt.show()

Matplotlib provides control over many properties of the objects comprising the figure, while

text can be rendered with LaTeX, as shown in the example. An example, showing how to plot

a curve that is given in parametric form, follows.

In [13]: plt.rcParams['axes.facecolor'] = [0.9, 0.9, 0.9]

In [14]: import matplotlib.pyplot as plt

In [15]: t = np.linspace(0.0, 2*np.pi, 1000)

In [16]: x = np.cos(20*t) * (0.5 - 0.5*np.sin(t))

In [17]: y = np.sin(20*t)

In [18]: plt.plot(x, y, linewidth = 2.0,

color = [0.92549, 0, 0.54902])lt.rc('text', usetex = True) In [19]: plt.show()

Page 9: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

To generate graphs of functions of two independent variables, say 𝑥, 𝑦, we first generate two

matrices X, Y that contain the coordinates of the points at which the function values are eval-

uated, using meshgrid; that is, once more, we generate a discrete version 𝑋ℎ of the domain 𝑋 =

[𝑎, 𝑏] × [𝑐, 𝑑] of the function 𝑓.

In [20]: t = np.linspace(-2.0, 2.0, 25)

In [21]: X, Y = np.meshgrid(t, t)

In [22]: plt.plot(X, Y, 'ok', color = [0.92549, 0, 0.54902])

In [23]: plt.show()

In [24]: X[0,0], Y[0,0]

Out[24]: (-2.0, -2.0)

In [25]: X[-1,-1], Y[-1,-1]

Out[25]: (2.0, 2.0)

Then, the standard procedure to obtain the values of 𝑓 follows and for instance, a filled con-

tour plot is generated with contourf.

Page 10: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

9

In [26]: t = np.linspace(-2.0, 2.0, 200)

In [27]: X, Y = np.meshgrid(t, t)

In [28]: Z = X * np.exp(-X**2 - Y**2)

In [29]: plt.contourf(X, Y, Z, 40, cmap = 'RdPu')

In [30]: plt.colorbar() In [31]: plt.show()

§4. Programming elements: functions, conditionals, and loops

Scripts and functions

Scripts are simple text files that contain Python statements, and have the extension .py. If a

script xmpl.py starts with the line #!/usr/bin/python, under the assumption that the Python

executable is located at /usr/bin/, then giving execution rights with chmod +x xmpl.py, results

in an executable file; that is, the code can be called by typing at the command line ./xmpl.py.

Python files may contain several function definitions. Python functions start with the key-

word def, as shown below;

def <function name> (<arg1>, <arg2>, ...):

<documentation string>

<function body>

return <expression>

The return statement exits a function, while optionally passing an expression to the caller. To

set the default value, for instance, for the second input argument, <arg2>=<value> is used in the

function definition. We assume that the following function is defined in the file xmplfun.py.

def fun(x, p = 2.0):

'Function with name fun, required input x, and optional input p'

return np.append(x, p*x**2)

Page 11: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

After entering in IPython run xmplfun.py, the fun function is called as any other Python func-

tion; that is,

In [1]: x = np.arange(3.)

In [2]: print(fun(x))

[ 0. 1. 2. 0. 2. 8.]

In [3]: print(fun(x, 1))

[ 0. 1. 2. 0. 1. 4.]

In [4]: print(fun(x, 2))

[ 0. 1. 2. 0. 2. 8.]

Single statement functions can be also defined using lambda functions; that is, anonymous

functions that take any number of input arguments and return a single expression;

<function name> = lambda <arg1>, <arg2>, ...: <expression>

As with regular functions, we can also assign default values to the input arguments of a lambda

function, as in the following example.

In [5]: find = lambda a, v = 0: np.nonzero(a > v)

In [6]: a = np.random.randn(5) In [7]: a

Out[7]: array([-1.50181503, -0.42390544, 0.56960244, 0.22188803, 2.16811881])

In [8]: idx = find(a)

In [9]: idx

Out[9]: (array([2, 3, 4]),)

In [10]: a[idx]

Out[10]: array([ 0.56960244, 0.22188803, 2.16811881])

Boolean expressions and if statements

A mathematical statement is a sentence that evaluates to either true or false. In computing

science, expressions that evaluate to either True or False are called Boolean and the operators

==, !=, <>, >, <, >=, <=, combined with the operators not, and, and or, are often used within if

statements in Python. Further, Python provides the membership operator (not) in, the iden-

tity operator is (not), and the bitwise operators >>, <<, &, ^, ~ that can be used to compare

integers in their binary formats.

def fun(x, y, tol = 1e-8):

print('Tolerance value: {}'.format(tol))

if np.abs(x - y) <= tol:

print('The given numbers are sufficiently close.')

else:

print('The given numbers are different.')

return

fun(np.sin(0.), np.cos(0.5*np.pi))

fun(np.sin(0.), np.cos(0.5*np.pi), 1e-20)

Page 12: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

11

Iterating with while and for statements

● A while loop repeatedly executes a set of statements, as long as a given condition evalu-

ates to True. The syntax of a while loop that we are mostly interested in is shown below.

<cnt> = <0> # initialize counter

while <expression>:

<statements>

<cnt> += <1> # increase counter by one, counter = counter + 1

● A for loop repeatedly executes a block of code, given a fixed number of times; that is, it

iterates over the members of a given sequence and hence, the membership operator in is em-

ployed.

for <cnt> in <sequence>:

<statements>

The loop statements can be terminated with the break statement, meaning that execution is

transferred to the statement that immediately follows the loop. Further, the continue state-

ment causes the loop to skip the remainder of its body and to immediately re-enter the loop.

We consider the following example.

s = ''

for char in 'no thing':

if char == ' ':

continue

s += char

print(s) # prints on the screen the word nothing, removes the space character

A1. The Van der Pol oscillator using SciPy

One of the most studied nonlinear stiff ODE problems is the nonlinear so-called Van der Pol

oscillator �̈� = 𝜇(1 − 𝑥2)�̇� − 𝑥, where 𝜇 > 0. This equation has been used in the study of cir-

cuits containing thermo-ionic valves, such as cathodic tubes in television sets or magnetrons

in microwave ovens. If we set 𝑦 = �̇�, we can write the Van der Pol equation as a first order

system; that is, �̇� = 𝑦 and �̇� = 𝜇(1 − 𝑥2)𝑦 − 𝑥. Below we define and solve an initial value

problem using the SciPy routine odeint.

import numpy as np

from scipy.integrate import odeint

import matplotlib.pyplot as plt

plt.rc('text', usetex = True)

plt.rc('font', family = 'serif', size = 16)

Page 13: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

# Define the ODE

def vdp(v, t, mu):

x, y = v

dvdt = [y, mu*(1.0 - x**2)*y - x]

return dvdt

# Initial condition

v0 = [0.1, 0.0]

# Time instances

t = np.linspace(0, 30, 500)

# Solver call

v = odeint(vdp, v0, t, args = (1.,))

# Graphical representation of the solution

plt.subplot(121)

plt.plot(t, v[:,0], linewidth = 2.0, color = [0.92549, 0, 0.54902], label = r'$x(t)$')

plt.plot(t, v[:,1], '--k', linewidth = 2.0, label = r'$y(t)$')

plt.xlabel(r'$t$')

plt.legend(loc = 'best')

plt.grid(True)

plt.subplot(122)

plt.plot(v[:,0], v[:,1], linewidth = 2.0, color = [0.92549, 0, 0.54902])

plt.xlabel(r'$x(t)$')

plt.ylabel(r'$y(t)$')

plt.grid(True)

plt.show()

A2. Sparse matrices and boundary value problems

A matrix is called sparse, when most of its elements are vanishing, otherwise it is called dense.

Sparse matrices can be stored using specialized techniques. A well-known sparse (banded, and

tridiagonal) matrix arises, when we approximate the second derivative of a function using

Taylor’s expansion; that is, 𝑓(𝑥 ± ℎ) ≈ 𝑓(𝑥) ± ℎ𝑓′(𝑥) + ℎ2𝑓′′(𝑥)/2. Addition of these

Page 14: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

13

approximations results in 𝑓(𝑥 + ℎ) − 2𝑓(𝑥) + 𝑓(𝑥 − ℎ) ≈ ℎ2𝑓′′(𝑥). Given the function values

𝒇 = [𝑓1, … , 𝑓𝑛]⊤ at the points 𝒙 = [𝑥1, … , 𝑥𝑛]⊤, we obtain 𝒇′′ ≈ 𝐓𝒇/ℎ2, where

𝐓 =

[ −2 1 0 ⋯ 0

1 −2 1 ⋱ 00 1 −2 ⋱ 0⋮ ⋱ ⋱ ⋱ 10 0 0 1 −2]

is the so-called Toeplitz matrix. To construct this matrix in Python, we build the first column

of 𝐓 ∈ ℝ5×5 and we call scipy.linalg.toeplitz, as follows.

In [1]: import numpy as np

In [2]: from scipy.linalg import toeplitz

In [3]: c = np.concatenate((np.array([-2., 1.]), np.zeros(3)), axis = 0)

In [4]: c

Out[4]: array([-2., 1., 0., 0., 0.])

In [5]: T = toeplitz(c)

In [6]: T

Out[6]:

array([[-2., 1., 0., 0., 0.],

[ 1., -2., 1., 0., 0.],

[ 0., 1., -2., 1., 0.],

[ 0., 0., 1., -2., 1.],

[ 0., 0., 0., 1., -2.]])

Since the Toeplitz matrix is dominated by zeros, we only need to store the non-zero entries.

SciPy provides several sparse storing formats. Here, we use the compressed column format

(CSC), meaning that the position indices and the values of the non-zero entries are stored, in

a column major order.

In [7]: from scipy.sparse import csc_matrix

In [8]: Ts = csc_matrix(T)

In [9]: print(Ts)

(0, 0) -2.0

(1, 0) 1.0

(0, 1) 1.0

(1, 1) -2.0

(2, 1) 1.0

(1, 2) 1.0

(2, 2) -2.0

(3, 2) 1.0

(2, 3) 1.0

(3, 3) -2.0

(4, 3) 1.0

(3, 4) 1.0

(4, 4) -2.0

In [10]: Ts.nnz # number of nonzero elements

Out[10]: 13

Given 𝐓, a numerical solution to the one-dimensional boundary value problem (BVP)

𝑢′′(𝑥) = −1 for all 𝑥 ∈ (0,1), 𝑢(0) = 𝑢(1) = 0, can be computed by solving the linear sparse

Page 15: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

system 𝐓𝒖 = −ℎ2diag 𝐈 with one of the solvers that are available in scipy.sparse.linalg, such

as spsolve (direct), bicg, bicgstab, cg, cgs, gmres, lgmres, minres, qmr, gcrotmk (iterative).

import numpy as np

from scipy.linalg import toeplitz

from scipy.sparse import csc_matrix

from scipy.sparse.linalg import spsolve

import matplotlib.pyplot as plt

plt.rc('text', usetex = True)

plt.rc('font', family = 'serif', size = 16)

h = 1e-3 # defines grid resolution

n = int(1/h) - 1 # number of points in [h, 1-h]

x = np.linspace(h, 1. - h, n)

c = np.concatenate((np.array([-2., 1.]), np.zeros(n-2)), axis = 0)

Ts = csc_matrix(toeplitz(c))

b = -h*h*np.ones(n)

u = np.concatenate(([0], spsolve(Ts, b), [0]), axis = 0)

# Graphical representation of the solution

x = np.concatenate(([0], x, [1]), axis = 0) # add boundary points

plt.plot(x, u, linewidth = 2.0, color = [0.92549, 0, 0.54902])

plt.xlabel(r'$x$')

plt.ylabel(r'$u(x)$')

plt.grid(True)

plt.show()

A3. The finite element method with FEniCS

Variational form of BVPs

The finite element method (FEM) is a flexible strategy for solving BVPs in complex-shaped

domains; it is based on the variational (or weak) form of a BVP and has rigid mathematical

foundations. The procedure for solving a BVP with the FEM can be decomposed into the

following sequential steps.

Page 16: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

15

● Derive the variational form of the given BVP.

● Generate a discrete version of the computational domain; for instance, subdivide the

domain into triangles.

● Choose trial functions 𝜙𝑖 and look for solutions of the form 𝑢 = ∑𝑢𝑖𝜙𝑖.

● Choose appropriate test functions.

● Assemble the matrix 𝐀 and the load vector 𝒃.

● Solve 𝐀𝒖 = 𝒃.

We introduce the FEM for the following BVP on a bounded domain Ω ⊂ ℝ2 with sufficiently

smooth boundary 𝜕Ω,

−Δ𝑢 = 𝑓 in Ω, 𝑢 = 𝑔 at ΓD,𝜕𝑢

𝜕𝑛= ℎ at ΓN.

Here, 𝑓, 𝑔, ℎ are known functions, while Δ ≡ ∇ ⋅ ∇ is the Laplace operator. The boundary 𝜕Ω

is divided into two parts ΓD, ΓN, according to the imposed boundary conditions (BCs). More

precisely, the BC at ΓD is a condition that defines the values of the state function 𝑢 itself; such

a condition is called a Dirichlet condition. On the other hand, the BC at ΓN prescribes the

values of the normal derivative 𝜕𝑢/𝜕𝑛 of the state function; such a condition is called a Neu-

mann condition. Here, 𝒏 = [𝑛𝑥, 𝑛𝑦]⊤

is the outward-directed unit normal at ΓN and the normal

derivative of 𝑢 at ΓN is

𝜕𝑢

𝜕𝑛= 𝒏⊤∇𝑢 = [𝑛𝑥, 𝑛𝑦] [

𝜕𝑢

𝜕𝑥,𝜕𝑢

𝜕𝑦]⊤

= 𝑛𝑥

𝜕𝑢

𝜕𝑥+ 𝑛𝑦

𝜕𝑢

𝜕𝑦.

When 𝑓 ≠ 0, the model problem is known as a Poisson BVP, whereas if 𝑓 = 0, the given

problem becomes a so-called Laplace BVP. The Poisson equation models a wide range of phe-

nomena, including heat conduction, electrostatics, diffusion of substances, twisting of elastic

rods, etc. A classical (or strong) solution to the model problem at hand is a function 𝑢 that is

at least twice continuously differentiable inside Ω and its first derivatives are continuous up

to the boundary. Reducing the regularity requirements for 𝑢 enables a generalization of clas-

sical solutions to what we call variational (or weak, due to weaker regularity assumptions)

solutions. To derive the variational problem associated with the given prototype model prob-

lem, the following steps are required.

● We define a set of smooth functions 𝑣 on Ω̅ = Ω ∪ 𝜕Ω, the so-called test functions, that

vanish at ΓD, written 𝑣|ΓD= 0. We always choose test functions that vanish at the bound-

aries, where Dirichlet BCs are imposed.

Page 17: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

● Given an arbitrary test function 𝑣, we multiply −Δ𝑢 = 𝑓 with 𝑣 and we integrate

throughout Ω; that is, −∫ 𝑣Δ𝑢Ω

= ∫ 𝑣𝑓Ω

. Using Green’s first identity ∫ 𝑣 ∂𝑢 𝜕𝑛⁄∂Ω

=

∫ ∇𝑣 ⋅ ∇𝑢Ω

+ ∫ 𝑣Δ𝑢Ω

we obtain ∫ ∇𝑣 ⋅ ∇𝑢Ω

− ∫ 𝑣 ∂𝑢 𝜕𝑛⁄∂Ω

= ∫ 𝑣𝑓Ω

.

● Finally, we use the Neumann BC at ΓN and the test function property 𝑣|ΓD= 0 to recast

the boundary integral ∫ 𝑣 𝜕𝑢/𝜕𝑛∂Ω

; that is, ∫ 𝑣 ∂𝑢 𝜕𝑛⁄∂Ω

= ∫ 𝑣 ∂𝑢 𝜕𝑛⁄ΓD

+ ∫ 𝑣 ∂𝑢 𝜕𝑛⁄ΓN

=

∫ 𝑣ℎΓN

.

Collecting the presented material, we conclude that each classical solution 𝑢 satisfies the so-

called variational problem;

find 𝑢 such that 𝑢|ΓD= 𝑔 and

∫∇𝑣 ⋅ ∇𝑢Ω

= ∫𝑣𝑓Ω

+ ∫ 𝑣ℎΓN

for all smooth 𝑣 that vanish at ΓD.

The functional space in which the solution to a variational problem is defined is called a trial

space. If 𝑔 = 0, then the trial space coincides with the test space; that is, the space of test

functions. Note that the Dirichlet BC is incorporated in the trial space; such a condition is

called an essential BC. On the other hand, the Neumann BC appears as a boundary integral in

the variational form and is called a natural BC. Here, for simplicity, we assume that 𝑔 = 0 and

hence, the trial and test spaces coincide. We denote this common space as

𝑉 = {𝑣: Ω → ℝ:∫ |𝑣|2 +Ω

∫ |∇𝑣|2

Ω

< ∞ and 𝑣|ΓD= 0},

where |∇𝑣| is the length of the gradient vector, |∇𝑣|2 = (𝜕𝑥𝑣)2 + (𝜕𝑥𝑣)2. Note that, although

defined on Ω, the functions 𝑣 in the definition of 𝑉 are continuously extended up to the bound-

ary with the restriction operator (⋅)|ΓD. Given 𝑉, the variational problem is compactly written:

find 𝑢 ∈ 𝑉 such that 𝑎(𝑣, 𝑢) = ℓ(𝑣) for all 𝑣 ∈ 𝑉, where 𝑎 and ℓ are the so-called bilinear and

linear forms; here, 𝑎(𝑣, 𝑢) = ∫ ∇𝑣 ⋅ ∇𝑢Ω

, ℓ(𝑣) = ∫ 𝑣𝑓Ω

+ ∫ 𝑣ℎΓN

.

The finite element discretization

The FEM discretizes the variational problem of a BVP by introducing a finite-dimensional

space 𝑉ℎ ⊂ 𝑉. The space 𝑉ℎ is equipped with a basis {𝜙𝑖}𝑖=1𝑁 , where dim(𝑉ℎ) = 𝑁. The discrete

problem reads: find 𝑢ℎ ∈ 𝑉ℎ such that 𝑎(𝑣, 𝑢ℎ) = ℓ(𝑣) for all 𝑣 ∈ 𝑉ℎ . Note that subtracting the

continuous and discrete versions of the variational problem, we obtain the so-called Galerkin

orthogonality

𝑎(𝑣, 𝑢 − 𝑢ℎ) = 𝑎(𝑣, 𝑢) − 𝑎(𝑣, 𝑢ℎ) = ℓ(𝑣) − ℓ(𝑣) = 0 for all 𝑣 ∈ 𝑉ℎ.

We express 𝑢ℎ as a finite sum using the chosen basis of 𝑉ℎ; that is, 𝑢ℎ = ∑ 𝑢𝑖𝜙𝑖𝑁𝑖=1 . Further,

since 𝑎(𝑣, 𝑢ℎ) = ℓ(𝑣) for all 𝑣 ∈ 𝑉ℎ, then 𝑎(𝑣, 𝑢ℎ) = ℓ(𝑣) holds true for the choice 𝑣 = 𝜙𝑖 and

Page 18: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

17

hence, for all 𝑖 ∈ {1,2, … , 𝑁} we obtain 𝑎(𝜙𝑖 , 𝑢1𝜙1 + 𝑢2𝜙2 + ⋯+ 𝑢𝑁𝜙𝑁) = ℓ(𝜙𝑖), which, due

to the linearity of 𝑎, can be written 𝑎(𝜙𝑖 , 𝑢1𝜙1) + 𝑎(𝜙𝑖 , 𝑢2𝜙2) + ⋯+ 𝑎(𝜙𝑖 , 𝑢𝑁𝜙𝑁) = ℓ(𝜙𝑖).

Since 𝑢𝑖 are scalars, we conclude that 𝑢1𝑎(𝜙𝑖, 𝜙1) + 𝑢2𝑎(𝜙𝑖, 𝜙2) + ⋯ + 𝑢𝑁𝑎(𝜙𝑖 , 𝜙𝑁) = ℓ(𝜙𝑖).

The last expression is the inner product of the vectors 𝒖 = [𝑢1, … , 𝑢𝑁]⊤ and 𝒂𝑖 =

[𝑎(𝜙𝑖 , 𝜙1), … , 𝑎(𝜙𝑖 , 𝜙𝑁)]⊤; that is, 𝒂𝑖⊤𝒖, or in matrix form, 𝐀𝒖 = 𝒃, where

𝐀 = [𝒂1

⋮𝒂𝑁

⊤] , 𝒃 = [

ℓ(𝜙1)⋮

ℓ(𝜙𝑁)].

Often, the basis functions {𝜙𝑖} are chosen to be element wise-defined polynomials of (rela-

tively low) degree 𝑛, resulting in the so-called Lagrange elements ℙ𝑛.

Python/FEniCS implementation

We consider a capacitor consisting of two elliptical plates with centers located at (±1,0) of

the 𝑥 axis, major axis equal to 2 and perpendicular to the 𝑥 axis, while the minor axis of each

plate is equal to 0.1. The capacitor is placed in free space. To perform computations, we trun-

cate the physical (infinite) domain using a circle of radius 5, centered at (0,0); the resulting

computational domain is denoted Ω, and does not contain the domains occupied by the plates

of the capacitor, denoted 𝜔L, 𝜔R. The truncating boundary Γt = 𝜕Ω ∖ (𝜕𝜔L ∪ 𝜕𝜔R) is assumed

to be sufficiently far and hence, grounded; that is, the potential is vanishing at Γt. Further, the

potential values at the plates are ±1 V, respectively. The strong form of the BVP describing

the physical setting is stated below;

Δ𝑢 = 0 in Ω, 𝑢|𝜕𝜔L= −1, 𝑢|𝜕𝜔R

= 1, 𝑢|Γt= 0.

To solve this problem using Python/FEniCS, we need to derive the variational form; find 𝑢 ∈

𝑉 such that 𝑎(𝑣, 𝑢) = ℓ(𝑣) for all 𝑣 ∈ 𝐻01(Ω), where 𝑉 is a function space that incorporates the

Dirichlet data, 𝐻01(Ω) = {𝑣: Ω → ℝ: ∫ |𝑣|2 +

Ω∫ |∇𝑣|2Ω

< ∞ and 𝑣|𝜕Ω = 0}, and 𝑎(𝑣, 𝑢) = ∫ ∇𝑣 ⋅Ω

∇𝑢 and ℓ(𝑣) = 0 are the forms that correspond to the BVP at hand. In FEniCS, we first define

the truncating domain Ω ∪ �̅�L ∪ �̅�R with Circle, from which we subtract the elliptical plates,

D1-D2-D3, while generating the mesh with generate_mesh. Three classes are defined and used to

identify the different Dirichlet boundaries, by assigning a unique boundary indicator to the

corresponding instances out, inl, inr using mark and MeshFunction. To define the finite element

space throughout the triangulation T we use FunctionSpace, while DirichletBC is used to assign

the corresponding Dirichlet data, given the constructed boundary indicators. The solution

object u is generated with TrialFunction, while the test functions v with TestFunction. The as-

signments a=dot(grad(u), grad(v))*dx and L=f*v*dx define the bi-linear and the linear form of

the variational problem, respectively. The rest of the code is self-explanatory and if not, we

suggest you consult “The FEniCS Tutorial” that can be found at https://fenicsproject.org/tu-

torial/.

Page 19: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

FOTIOS KASOLIS | PYTHON FOR SCIENTIFIC COMPUTING

from fenics import *

from mshr import *

import numpy as np

import matplotlib.pyplot as plt

plt.rc('text', usetex = True)

plt.rc('font', family = 'serif', size = 16)

# Generate domains

D1 = Circle(Point(0.0, 0.0), 5.0)

D2 = Ellipse(Point(-1.0, 0.0), 0.1, 2.0)

D3 = Ellipse(Point(1.0, 0.0), 0.1, 2.0)

# Generate mesh

T = generate_mesh(D1 - D2 - D3, 64)

# Identify the boundaries

class OUT(SubDomain):

def inside(self, x, on_boundary):

return x[0]**2 + x[1]**2 > 24 and on_boundary

class INL(SubDomain):

def inside(self, x, on_boundary):

return x[0] < 0.0 and x[0] > -2.0 and x[1] < 2.5 and x[1] > -2.5 and on_boundary

class INR(SubDomain):

def inside(self, x, on_boundary):

return x[0] > 0.0 and x[0] < 2.0 and x[1] < 2.5 and x[1] > -2.5 and on_boundary

out = OUT()

inl = INL()

inr = INR()

# Assign boundary indicators

boundaries = MeshFunction('size_t', T, T.topology().dim() - 1)

out.mark(boundaries, 0)

inl.mark(boundaries, 1)

inr.mark(boundaries, 2)

# Define finite element space

V = FunctionSpace(T, 'P', 1)

# Assign Dirichlet conditions

bcD1 = DirichletBC(V, 0.0, boundaries, 0)

bcD2 = DirichletBC(V, -1.0, boundaries, 1)

bcD3 = DirichletBC(V, +1.0, boundaries, 2)

bc = [bcD1, bcD2, bcD3]

# Define variational problem

u = TrialFunction(V)

v = TestFunction(V)

f = Constant(0.0)

a = dot(grad(u), grad(v))*dx

L = f*v*dx

# Compute solution

u = Function(V)

Page 20: Python for Scientific Computing - WordPress.com · Python for Scientific Computing a tutorial introduction FOTIOS KASOLIS. Version 15/05/2018 §1. The fundamentals of Python Simple

19

solve(a == L, u, bc)

# Plot solution

p = plot(u, cmap = 'RdPu')

plt.colorbar(p)

plt.show()