Approximation of a finite element function in DOLFINx#
In this section, we will combine all the components presented in the previous sections to solve the projection problem To solve this problem, we create the variational form
Creating a simple mesh in DOLFINx#
As you might have seen in other FEniCSx tutorials, there are some “built-in” meshes in DOLFINx.
In this tutorial, we will use a unit square, consisting of triangular elements, where we have 10
elements in each direction.
To create a mesh, we need to decide on what MPI communicator we want to use.
For all the examples in this tutorial, we will use the communicator MPI.COMM_WORLD
.
from mpi4py import MPI
import numpy as np
import pyvista
import dolfinx.fem.petsc
import ufl
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10)
Next, we create the function space for our unknown u
. In this example,
we use a discontinuous Lagrange element of degree 3.
V = dolfinx.fem.functionspace(mesh, ("Discontinuous Lagrange", 3))
Next, we can write the bi-linear form a
and the linear form L
.
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
a = u * v * ufl.dx
Note
Compared to the previous sections, we have now used dolfinx.fem.Function
, instead of ufl.Coefficient
to defined f
and g
.
This is because a ufl.Coefficient
does not hold any data. The dolfinx.fem.Function
is sub-classed from ufl.Coefficient
and holds data for the degrees of freedom.
F = dolfinx.fem.functionspace(mesh, ("Discontinuous Lagrange", 2))
f = dolfinx.fem.Function(F)
G = dolfinx.fem.functionspace(mesh, ("Lagrange", 2))
g = dolfinx.fem.Function(G)
L = f / g * v * ufl.dx
We populate the degrees of freedom of f
and g
with some data.
We use a lambda-function
to define an operator that takes in a set of points x
as a numpy
2D array
(of the shape (3, num_points)
) and returns an array of shape num_points
with the data at the input points.
Note
In DOLFINx, the input data to the interpolation operator has the shape (3, num_points)
and not (num_points, 3)
.
This might seem confusing at first, as you can’t access the i
th point as x[i]
,
but you have to access it as x[:, i]
.
However, this is beneficial when you can use vectorized operations, such as numpy.sin
,
which can operate on the entire array at once.
g.interpolate(lambda x: 0.8 + np.sin(np.pi*x[1]))
As f
is in a discontinuous function space, we will create a piecewise constant function over parts of the domain.
We do this by first locating what cells satisfies x<=0.5
(i.e. all vertices of the cell satisfies this condition).
left_cells = dolfinx.mesh.locate_entities(mesh, mesh.topology.dim, lambda x: x[0] <=0.5+1e-14)
Next we populate all degrees of freedom by the function we want in the rest of the domain, \(f(x,y) = x\).
f.interpolate(lambda x: x[0])
And then we overwrite the values in the left cells with the function \(f(x,y) = 1\).
f.interpolate(lambda x: np.full(x.shape[1], 1, dtype=dolfinx.default_scalar_type), cells=left_cells)
As we are only working on a sub-set of cells, we call scatter_forward
to ensure that all processes on a distributed
system gets the correct values.
f.x.scatter_forward()
Next we need to solve the linear problem. We do this using PETSc, which is an interface to many linear algebra libraries,
as well as providing it’s own set of solvers.
In DOLFINx, we provide a simple interface to PETSc with the dolfinx.fem.petsc.LinearProblem
class.
If we want to give PETSc some options, such as what direct solver to use, we can do this with a dictionary.
In the following example, we will use a direct solver, and specifically the MUMPS solver.
petsc_options = petsc_options={"ksp_type": "preonly",
"pc_type": "lu",
"pc_factor_mat_solver_type": "mumps"}
problem = dolfinx.fem.petsc.LinearProblem(a, L, petsc_options=petsc_options)
Now we can solve the problem and plot the solution.
uh = problem.solve()
def plot_scalar_function(u: dolfinx.fem.Function):
pyvista.start_xvfb(1.0)
u_grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(u.function_space))
u_grid.point_data["u"] = u.x.array
linear_grid = pyvista.UnstructuredGrid(*dolfinx.plot.vtk_mesh(u.function_space.mesh))
warped = u_grid.warp_by_scalar()
plotter = pyvista.Plotter()
plotter.add_mesh(linear_grid, style="wireframe", color="black")
plotter.add_mesh(warped)
plotter.show_axes()
plotter.show()
plot_scalar_function(uh)