Introduction to Elementary FEM
I have recently been working on a project that involves using the Finite Element Method. Here is a sneak peak:
To begin what is hopefully going to be a series of blog posts, I would like to introduce the basic concept of the Finite Element Method. This note is aimed at around undergraduate level for vaguely STEM-ish fields with solid mathematical background. I tried to be a bit less formal than I would usually write these things, hopefully it makes this note less intimidating and more approachable.
To begin, let's consider solving a simple PDE in 1D. Let be a continuous function. We are looking for a sufficiently differentiable function such that.*∂ denotes the derivative operator. That is, ∂u is the derivative of u and ∂²u is the second derivative of u.
This is nothing special, just finding the second antiderivative of . The boundary conditions are somewhat unimportant in this case, since one may always add a linear polynomial to the solution to satisfy them. This becomes more interesting in higher dimensions.
As one may expect, computing the function may be difficult, entirely depending on the form of . This means we need to look for an approximation.
To start, let's play a game:
First, let's introduce some notation. We are going to define the inner product of two reasonably nice functions and on the interval by:
Let's say that solves the PDE. Then, let's grab any other twice differentiable function that is also zero on the boundary, that is, (meaning, it could plausibly be a solution of the PDE). and compute the following integral using integration by parts:
Rearanging, we get the equality
It is an exercise in analysis to show that, this condition is in fact sufficient: if satisfies this condition for every twice differentiable function that is zero on the boundary, then is a solution of the PDE. This is called the weak form of the PDE.
It is not immediately obvious how this is useful. On a first glance, we have converted a problem that requires us to find a function which has a certain property into a problem that requires us to find a function that satisfies a certain integral equation for every function . This does not seem to be easier at all!
Now comes the main trick: instead of considering the entire space of twice differentiable functions that are zero on the boundary, we are going to use a different, finite dimensional space of functions. This space does not even need to be a subspace of the space of smooth functions and in fact, it usually isn't in practical implementations.
Definition. For the remainder of this post, let
be a partition of the unit interval. Furthermore, let the solution space, denoted by , be the vector space of functions on the unit interval that are
- continuous
- piecewise linear, that is, linear polynomial on every interval
- zero on the boundary, that is, for every
The functions in this space look like this:
In more theoretical texts, the first and second conditions are often abstracted, for instance by considering polynomials of arbitrary degree.
It is worth observing that in practice, we are the ones that choose the partition . This means that if we have some knowledge about the function , or maybe we demand better accuracy in some regions, we can increase the density of the partition in those regions.
Definition. A weak solution is a function such that, for every , the following holds:
I have smuggled in another trick here: notice that the weak form only requires once-differentiable functions. This is convenient since the second derivatives of our piecewise linear functions are zero almost everywhere. This allows us to work with simpler functions, simplifying the computation later.
Now for the final trick: notice that is a finite dimensional vector space. This means that we can choose a basis of , say . We use this basis to write as a linear combination of the basis functions:
It is now an easy exercise in linear algebra to show that it is sufficient for the weak solution to satisfy the weak form for every basis function of instead of every function in . That is, we assemble a system of equations:
Or in matrix form:
Using fancy terminology, the matrix on the left is the Gram matrix of the inner product*Verifying that this is an inner product is left as an exercise to the reader. More advanced treatments tend to allow more general bilinear forms here. on the space . Engineers usually call this matrix the stiffness matrix. It can be shown that this system of equations always has a solution.
What remains is picking the basis functions. Let's consider several demands we would like to impose on the basis:
- The stiffness matrix should be reasonably sparse.
- The right hand side terms should be easy to compute or at least approximate.
- Once we get the solution , we should be able to compute the function easily from the coefficients.
The usual basis functions are the hat functions. These are piecewise linear functions that look like this:
That is, an increasing linear slope between and and a decreasing linear slope between and . In more precise terms:
These functions have some desirable properties. First, note that if we have , then , since the intervals where the functions are non-zero do not even overlap. This means that the stiffness matrix is tridiagonal, which satisfies our sparsity requirement. There are even specialized algorithms that can be used to solve systems of equations that have a tridiagonal matrix. It is not that easy in higher dimensions, but even there, methods that work well with sparse matrices are available.
Computing the right hand side terms may be a bit more difficult, depending on the form of . We are looking to evaluate the integral
In practice, we use a numerical integration scheme here. An easy approach is to assume that , that is, we linearly interpolate from its values on . Then, we can simply compute the integral directly. Even simpler approach is to assume exploit the locality of the basis functions, assuming that is constant on and compute
Do note that this, again, splits our right hand side into a part that can be pre-computed for a given partition and a part that depends on the function .
And finally, since the basis functions are setup in a way that only is non-zero on , computing the value of the solution is simply: