Extended documentation for skfem (draft) #817
Replies: 3 comments 19 replies
-
This looks fantastic. I will read it through and try to make some comments. |
Beta Was this translation helpful? Give feedback.
-
Do you see this as part of https://scikit-fem.readthedocs.io/en/latest/? The positive side of Sphinx is that all the snippets can be doctests so they would be tested in CI. Would it go under |
Beta Was this translation helpful? Give feedback.
-
Whatever happened with this effort? I just read through the draft and it is fantastic--it answered a number of questions I had and illuminated some areas I was completely unaware of. Is there a plan to get this into the documentation? I think it would help a lot of people judging by how much it has helped me. |
Beta Was this translation helpful? Give feedback.
-
This discussion assumes familiarity with
numpy
andmatplotlib.pyplot
.It is extremely helpful when working with
skfem
to understand the concepts of quadrature points and degrees-of-freedom, and how these concepts relate to symbolic math expressions, python functions, and real-world data. This all happens in the umbrella concept of a "mesh" and which also has to be reasonably well understood to useskfem
. To illustrate how these components fit together, we start with a simple 2D mesh of triangles created directly fromskfem
.The mesh is represented as two lists of information. The first is a list of points describing where vertexes are and the second is a list of connections between the points to form elements. These connections are the "topology" of the mesh.
This is easy enough to draw in matplotlib. The first row in the points array contains the x locations of each point and the second row contains the y locations.
The topology (
mesh.t
) specifies how these points are connected together to form triangles. Each column specifes the indexes of the points that defined the vertexes of the triangle. We can add these lines to the plot.This is the most basic triangulation of the unit square.
skfem
has utilities to refined this mesh into more and smaller triangles as well as translate it or map it into different coordinates. It also has visualization functions to make drawing the mesh on anmatplotlib
axis easier.The
skfem
documentation and code uses several terms when working with meshes of one, two, or three dimensions that are worth clarifying before we proceed. These arecells
,facets
,edges
, andnodes
, and are best illustrated with a picture:Using this naming convention,
facets
are always shared between elements and one dimension lower than the mesh.nodes
are always at the vertices of the mesh. This picture also illustrates quadrilateral meshes, which are an alternative to triangulations that can be generated byskfem
. For the remainder of this discussion, we will work with 2D triangular meshes.Meshes form a kind of coordinate system to work in, and we construct a set of basis functions in this system by specifying a functional form over one cell of the mesh. This discussion will be limited to two kinds of basis functions: ones that are constant over the cell and ones that are linear over the cell.
skfem
calls theseElementTriP0
andElementTriP1
, respectively. Note that these two basis sets have different continuity characteristics between cells. Basis functions inElementTriP0
are discontinuous between cells. Basis functions inElementTriP1
are continuous between adjacent cells, but their derivatives are not.We continue this discussion by building a set of basis functions using
ElementTriP1
over the once refined triangulation of the unit square discussed above.What we get back after this call is a python
object
of typeCellBasis
. This is a mostly opaque object that we can use to work with the set of basis functions that span our finite element space. Functions represented in this finite space are (obviously) described by a finite number of parameters, inskfem
called degrees of freedom (dofs). In ourP1
space that we've constructed, this will always be equal to the number of nodes in the mesh. However, this is in general not true, so to get anumpy
array of the correct length and initialized to zeros, we will use our basis object.Although this is a simple
numpy
array, there are not many things we can do with it directly, since out at this level of the code we don't know anything about what the array index means. Its primary application in our code will be controlling Dirichlet boundary conditions: those locations on the mesh where we already know the value of the solution. We can experiment with this by projecting a constant function of1
into the finite element space, and then showing how we can manipulate this function using ourbasis_p1
object and thefe_approximation
array. For now, we will also make use of another helper function fromskfem
to visualize the functions we construct. Later we'll explore other ways to interrogate and visualize functions we've represented in our finite element space.Now, suppose we want to change this function so it is
0
on the left edge. To tellskfem
to make the function zero along those vertical line segments on the left edge, we'll call on a very powerful and flexible feature of our basis object:get_dofs()
.We can use this function to make
skfem
return the indexes to use withfe_approximation
in order to specify the value of our function in two ways: along facets and over entire triangles (skfem
calls these triangles "elements". In this context, "element" is purely geometrical and should not be confused with the "finite element" which includes a concept of polynomial degree.)We could make a more complicated function, leaving
0
on that left edge, and going to2
on the top edge. Here we use alambda
function to make the code more compact. In general though,lambda
functions should only be used in trivial circumstances. The verbose naming above is more descriptive and readable.In a directly analogous manner, we can specify values over entire elements instead of just edges.
This is exactly correct. The function is
0
everywhere in the bottom left triangle, and goes linearly (because we're inP1
) to1
outside of this triangle. Note the continuity between triangles, another consequence of usingP1
to form our basis set.To summarize our discussion so far, we've seen how to construct a finite element basis set from a mesh and a choice of function over one cell of that mesh, in our case
P1
(linear polynomials). And we've now seen how to create simple functions in that space by specifying the value of the function everywhere ([:]
), along facets (get_dofs(facets=)
) or over elements (get_dofs(elements=
).Lets take a closer look at what is happening when we supply a function to
get_dofs()
by tricking it into plotting the query locations it is using. Note the use oflambda
here to supply most of the arguments to our trial function while still leavingx
available as an argument forget_dofs()
.This plot shows the
x
coordinates supplied to our test function. If we returnTrue
for one of these coordinates, thenget_dofs()
will return the indexes required byfe_approximation
to force that element or facet to a specified value.The extremely important caveat here is that one should never use
==
when dealing with floating point numbers. Therefore, to find those two red dots on the vertical pair of facets in the center, we should write as follows. (Later we will show more robust and precise ways of labelling facets and elements during mesh construction.)Another way to construct a function in the finite element space is by projection. To demonstrate this, we'll use
f(x) = abs(x[1]-0.5)
which would be a horizontal valley running along the line atx[1]=0.5
. We'll use anskfem
utility which uses a basis object to project a python function into the finite element space. That python function must accept a single argument of point vectors and return an array of function values at those points.Compare the similarities between this example and the previous one to see how there may be more than one way to construct the same function in our finite element space. From this point forward, we will refer to this process generically as "projecting into the finite element space" regardless of which of the methods was actually used to generate the projection.
The
basis_p1
object and thefe_approximation
array that we've been working with are abstract representations of our function in the finite element space. Internally,skfem
samples this function at a set of locations called "quadrature points".skfem
uses weight sums of these samples to compute the integrals it uses to solve PDEs.These samples at quadrature points are another way to represent the functions we have projected into finite element space and it is important to understand their relationship with the projections we've been constructing. To start this discussion, however, it is important to distinguish between "local" coordinates and "global" coordinates. In this triangulation we've been working in, the local, or reference, triangle is on with vertexes and
(0,0)
,(1,0)
, and(0, 1)
.Each of the triangles in our mesh can be individually be transformed into these coordinates, i.e. for the purposes of integration. The quadrature points used are available via the basis object we constructed previously, so we can plot their locations on the reference triangle.
We can get a global visualization of the quadrature points by reverse mapping the local coordinates to each of the triangles in our mesh.
The global_points array is organized as (coordinate, element_index, quadrature_index):
To demonstrate how interpolation works, let's annotate each of those quadrature points with the values of a (projected) function sampled at those locations. To do this, we'll use the
interpolate
method of our basis object on a function projected into finite element space.The number of quadrature points in an element controls the level of accuracy of the integrations. For low degree polynomial basis functions, one can supply enough quadrature points for exact integration, where the only source of error is the finite machine precision of the computer. Using more quadrature points than necessary does not further improve accuracy and slightly increases computation time, but it can provide a common space to perform computations on functions that were projected into different finite element spaces. For this reason, it is usually preferred to construct the highest order basis set first (in the present consideration that is
P1
) and then derive the lower order basis set from it. This will ensure the basis sets share a common set of quadrature points, and that there are enough quadrature points to perform exact integration of the highest order basis set.The
P0
space has functions that are constant over a cell in the mesh and consequently discontinuous on the facets between cells. It also has fewer degrees of freedom than aP1
basis constructed on the same mesh. Specifically, theP0
basis will have a degree of freedom for each element in the mesh.Functions can be projected into the
P0
space in the same ways that were used forP1
projection:get_dofs()
andprojection()
. As the first example, we will examineget_dofs()
and compare it to one of the previous examples we used inP1
: the lower left triangle should be0
and1
everywhere else in the mesh.A second example of functions projected into
P0
usesprojection()
:It is critical to realize in both of the previous two examples, the identical "real" function was projected into each of the
P1
andP0
spaces, and the plots are showing the closest approximation to the "real" function available in the respective spaces.To gain further insight into how well these projections are matching our "real" functions, it would be useful to examine these projections along a line through the space. For this, we can use the
probes
method on the basis objects we have constructed. Let's usef(x) = 4*abs(x[0]-0.5)
to illustrate how this works.One more example, using a more refined mesh and a
f(x) = sin(2*pi*x[1])
. Note that since we are changing the mesh here, we must also reconstruct the basis objects.Edit 1: Changed typo facets= to elements=
Edit 2: Changed typo P1 to P0 in text.
Beta Was this translation helpful? Give feedback.
All reactions