Dune-Fufem 2.11-git
Loading...
Searching...
No Matches
Poisson equation (python)

Full code of this example This example on gitlab

Introduction

The following example shows how to solve a Poisson problem with Dirichlet boundary conditions using second order finite elements with the python interface of dune-fufem.

Setup and initialization

The example first imports the required modules from numpy, scipy, and dune and the local modules required for dune-fufems python bindings.

4import numpy as np
5import scipy.sparse.linalg
6
7import dune.common
8import dune.geometry
9import dune.grid
10import dune.functions
11
12from dune.grid import DataType
13from dune.functions import defaultGlobalBasis, Power, Composite, Lagrange
14
15from fufemforms import *
16from utilities import *
17

Define problem data

The example solves the Poisson equation with Dirichlet boundary conditions. The problem data consisting of the right-hand-side and the Dirichlet values and an indicator for the Dirichlet boundary are given as functions.

18
19
20# Scalar right hand side
21rhs = lambda x: 10
22
23# Dirichlet boundary values
24dirichletValues = lambda x : np.sin(2*np.pi*x[0])
25
26def isNear(a,b):
27 return np.abs(a-b) <= 1e-10
28
29# Indicator function of Dirichlet boundary
30isDirichlet = lambda x : isNear(x[0], 0) or isNear(x[0], 1) or isNear(x[1], 0) or isNear(x[1], 1)
31

Create grid and finite element basis

The example discretizes the problem using a structured grid on the unit square.

Note
There is an inconsistency in the terminology of the Python bindings with C++. What is denoted as grid in Python actually refers to the leaf GridView in the C++ interface of Dune.
32
33
34# Number of grid elements (in one direction)
35gridSize = 32
36
37# Create a grid of the unit square
38grid = dune.grid.structuredGrid([0,0],[1,1],[gridSize,gridSize])
39

As a next step the program creates a global finite element basis on the grid. The helper function Lagrange(order=2) declares that we want to use Lagrange finite elements of order two, while the defaultGlobalBasis(...) function creates the basis of this type on the given grid.

40
41
42# Create a nodal Lagrange FE basis
43basis = defaultGlobalBasis(grid, Lagrange(order=2))
44
45print("Dimension of FE space is "+str(len(basis)))
46
void print() const

Create constraints data structures

To handle the Dirichlet boundary conditions we first mark all constrained DOFs by interpolating the Dirichlet boundary indicator function and then interpolate the Dirichlet values on the constrained DOFs.

47
48
49# Define Dirichlet constraints
50N = len(basis)
51
52# Mark all Dirichlet DOFs
53isConstrained = np.zeros( N, dtype=bool )
54basis.interpolate(isConstrained,isDirichlet)
55
56# Interpolate the boundary values
57constraintDOFValues = np.zeros( N )
58basis.interpolate(constraintDOFValues, dirichletValues)
59

Assemble the variational problem

Inspired by the UFL language of the Fenics project project, dune-fufem allows to define local assemblers for linear and bilinear forms using simple expressions. We first obtain expressions for trial and test functions associated to the basis. The only difference between trialFunction() and testFunction() is that the resulting objects later refer to column and row indices, respectively. In order to use the right-hand-side functions within a form expression we declare it as coefficient function, i.e. a function not depending on test and trial functions. Now we can define the bilinear and linear form in a straight forward way.

60
61
62# Trial and test functions for variational problem
63u = trialFunction(basis)
64v = testFunction(basis)
65
66# Scalar rhs coefficient function
67f = Coefficient(rhs, basis.gridView)
68
69# Bilinear form and rhs functional
70a = integrate(dot(grad(u),grad(v)))
71l = integrate(f*v)
72
field_type dot(const type &newv) const

The defined bilinear form and linear form objects satisfy the interfaces for local matrix and vector assemblers and can thus directly be passed to a global assembler for assembling the actual matrix and right-hand-side vector. Once the system is assembled, we can constrain it to the affine subspace satisfying the constraints that we defined earlier.

Note
While the Python bindings support thread-parallel assembly this is disabled by default and not used in the example. The reason for this is, that Python itself is not thread-safe. Hence, we must not use the thread-parallel assembler when passing Python functions to the C++ assembler as we do for the right-and-side in this example.
73
74
75# Assemble into matrix and vector
76assembler = GlobalAssembler(basis)
77A = assembler.assembleOperator(a)
78b = assembler.assembleFunctional(l)
79
80# Build constraints into matrix
81incorporateEssentialConstraints(A, b, isConstrained, constraintDOFValues)
82

Solve the algebraic problem

To solve the linear system of equations we can use the build in sparse direct solvers of the scipy.sparse module.

83
84
85# Solve linear system!
86x = scipy.sparse.linalg.spsolve(A, b)
87

Write solution to VTK

Finally we can write the solution to a VTK file understood by the ParaView visualization program. To this end we first create a finite element function by combining the basis with the coefficient vector using basis.asFunction(...). Then we create a writer object with two levels of subsampling and pass the function to the writer, specifying the name and data type of the resulting field in the output.

88
89u = basis.asFunction(x)
90
91vtk = grid.vtkWriter(2)
92u.addToVTKWriter("sol", vtk, DataType.PointData)
93vtk.write("poisson-pq2-py")
94