FEniCS differences between Function, TrialFunction and TestFunction

Posted on Thu 06 February 2020 in finite element method, IT, programming, mathematics

The FEniCS project allows for simple solution of partial differential equations. However, getting started from examples is so quick, it is easy to miss how the inner-workings of it behave. This should not happen (especially if it is a PhD project). There are three vital pieces of the puzzle that it might not be clear what they are for, and the documentation does not help here: the functions TrialFunction, TestFunction, and Function.

The thing is that, in the usual Finite Element Methods, we only have the distinction between the trial function \(u \in V\_h\) and test function \(v \in V\), and even then the distinction is often blurry. In fact, technically, the spaces \(V\_h\) and \(V\) can be different. However, most of the time we take them to be the same (for example, the same discrete space of piecewise linear functions). The names we give them is just to have clearer in our heads what role they play in the game, but we could as well say "two functions in \(V\)". Then why would FEniCS have even a three-fold difference? You can quickly check for yourself that, for example, if you swap the TrialFunction for a Function, FEniCS will complain.

There is no documentation on the topic as far as I have found, so these are my (maybe mistaken) deductions.

The meaning of the TrialFunction is to let FEniCS know what function we are solving for. In fact, when we call

solve(A, u_.vector(), b)

FEniCS expects to find a TrialFunction in A, which is supposed to be the function we are looking for. On the other hand, u_ is the symbol in which the result will be stored, and this needs to be a Function (not Trial, not Test, just a regular Function).

What about the TestFunction? FEniCS expects one as well in A, but why would it care? Well, the way we obtain a (finite) linear system to solve when using FEM, is by rewriting the problem in terms of the basis hat functions \(\phi_i\). In fact, when solving the problem

\(\int u\_h' v' dx = \int f v dx\)

exploiting the fact that \(v \in V = \{ \text{piece-wise linear functions null at borders} \}\), we can rewrite it in terms of the basis of \(V\) as

\(\int u\_h' \phi\_i' dx = \int f \phi\_i dx, \ \ i = 1, \cdots, n-1\)

and here is where the role of the TrialFunction comes in! If FEniCS knows \(v\) is a trial function, then it can do this replacement with basis functions and effectively build the linear system.

Technically, TrialFunction, TestFunction and simple Function can be distinguished either because they support different methods, as shown in the screenshot below, or by checking the type of a given variable. TrialFunction and TestFunction are of type dolfin.function.argument.Argument, which is the same as fenics.function.argument.Argument, while Function is of type dolfin.function.function.Function.

fenics trialfunction testfunction funciton

Further technical details: by default, TrialFunction is an Argument with number == 1, while TestFunction has number == 0 (check /dolfin/function/argument.py). Argument numbers can be checked through u.number():

import fenics as fen
mesh = fen.UnitSquareMesh(10,10)
V = fen.VectorFunctionSpace(mesh, 'P', 2)

trial = fen.TrialFunction(V)
test = fen.TestFunction(V)

trial.number() == 1 # True
test.number() == 0  # True

fenics

So, in the end:

  • FEniCS expects at least one TrialFunction and at least one TestFunction in each variational problem to be solved.
  • FEniCS expects no more than one TrialFunction per problem (cannot mixed them!).