B-splines FEM solver for Poisson equation (1D)#
Following the previous section, we implement here a B-Splines FEM for the Poisson problem in 1D, with homogeneous boundary conditions.
Appropriate data-structures for matrices and vectors, involved when assembling a bilinear and linear form, will be discussed in the next section.
We consider the following Poisson problem in 1D: $\( -u^{\prime \prime} = f, \quad (0,1) \\ u(0) = 0 ~~ \mbox{and} ~~ u(1) = 0 \)$
Using the algorithms developped in section 1., for assembly a bilinear form and a linear form, we see that we need to assemble
The Stiffness matrix
The right hand side (rhs)
# needed imports
from numpy import zeros, ones, linspace, zeros_like, asarray
from matplotlib.pyplot import plot, show
%matplotlib inline
# ... assembling the stiffness matrix using stencil forms
def assemble_stiffness(nelements, degree, spans, basis, weights, points, matrix):
# ... sizes
ne1 = nelements
p1 = degree
spans_1 = spans
basis_1 = basis
weights_1 = weights
points_1 = points
k1 = weights.shape[1]
# ...
# ... build matrices
for ie1 in range(0, ne1):
i_span_1 = spans_1[ie1]
for il_1 in range(0, p1+1):
for jl_1 in range(0, p1+1):
i1 = i_span_1 - p1 + il_1
j1 = i_span_1 - p1 + jl_1
v = 0.0
for g1 in range(0, k1):
bi_0 = basis_1[ie1, il_1, 0, g1]
bi_x = basis_1[ie1, il_1, 1, g1]
bj_0 = basis_1[ie1, jl_1, 0, g1]
bj_x = basis_1[ie1, jl_1, 1, g1]
wvol = weights_1[ie1, g1]
v += (bi_x * bj_x) * wvol
matrix[i1, j1] += v
# ...
return matrix
# ...
# ... Assembly procedure for the rhs
def assemble_vector(f, nelements, degree, spans, basis, weights, points, rhs):
# ... sizes
ne1 = nelements
p1 = degree
spans_1 = spans
basis_1 = basis
weights_1 = weights
points_1 = points
k1 = weights.shape[1]
# ...
# ... build rhs
for ie1 in range(0, ne1):
i_span_1 = spans_1[ie1]
for il_1 in range(0, p1+1):
i1 = i_span_1 - p1 + il_1
v = 0.0
for g1 in range(0, k1):
bi_0 = basis_1[ie1, il_1, 0, g1]
bi_x = basis_1[ie1, il_1, 1, g1]
x1 = points_1[ie1, g1]
wvol = weights_1[ie1, g1]
v += bi_0 * f(x1) * wvol
rhs[i1] += v
# ...
# ...
return rhs
# ...
# ...
Note: We added the function f as an argument of the assemble_rhs function. In general, we will avoid calling a function over the quadrature points for performance reasons.
We will need the following imports from the psydac library.
# nedded imports
from psydac.core.bsplines import elements_spans # computes the span for each element
from psydac.core.bsplines import make_knots # create a knot sequence from a grid
from psydac.core.bsplines import quadrature_grid # create a quadrature rule over the whole 1d grid
from psydac.core.bsplines import basis_ders_on_quad_grid # evaluates all bsplines and their derivatives on the quad grid
from psydac.utilities.quadratures import gauss_legendre # computes gauss legendre quadrature points & weights
from utilities.plot import plot_field_1d # plot a solution for 1d problems
Now, let’s create a B-spline Finite Element Space \(V\) over a grid of \(32\) elements, and using cubic B-splines.
A B-Spline space is defined here through a grid, we then call the function make_knots to create an open knot sequence. We finally, compute the spans for every element, to avoid doing it for each quadrature point on the current element, in the assembly procedure.
p = 3 # spline degree
ne = 32 # number of elements
grid = linspace(0., 1., ne+1)
knots = make_knots(grid, p, periodic=False)
spans = elements_spans(knots, p)
nelements = len(grid) - 1
nbasis = len(knots) - p - 1
# we need the value a B-Spline and its first derivative
nderiv = 1
# create the gauss-legendre rule, on [-1, 1]
u, w = gauss_legendre( p )
# for each element on the grid, we create a local quadrature grid
points, weights = quadrature_grid( grid, u, w )
# for each element and a quadrature points,
# we compute the non-vanishing B-Splines
basis = basis_ders_on_quad_grid( knots, p, points, nderiv, normalization=False )
Assembling the Stiffness matrix is then done using
stiffness = zeros((nbasis, nbasis))
stiffness = assemble_stiffness(nelements, p, spans, basis, weights, points, matrix=stiffness)
Analytical solution#
In finite elements, one of the first tests we do is to check polynomial solutions of with a degree that smaller or equal to the one we chose for our discretization. Here, we take the analytical solution \(u(x) = x(1-x)\) which leads to the rhs \(f(x) = - \partial_{xx} u(x) = 2\).
f = lambda x: 2.
rhs = zeros(nbasis)
rhs = assemble_vector(f, nelements, p, spans, basis, weights, points, rhs=rhs)
In order to apply the homogeneous Dirichlet boundary conditions, we only have to remove the first and the last splines, since their values at the boundary is \(1\). Due to the partition of unity, the remaining splines are \(0\) on the boundary.
# apply homogeneous dirichlet boundary conditions
rhs = rhs[1:-1]
stiffness = stiffness[1:-1, 1:-1]
We use a cg solver from scipy to solve the linear system.
from scipy.sparse.linalg import cg
u, info = cg( stiffness, rhs, rtol=1e-6, maxiter=5000 )
Now, we need to add the first and last control points, which are equal to 0, then proceed to the plot.
u = [0.] + list(u) + [0.]
u = asarray(u)
plot_field_1d(knots, p, u, nx=401)
The Spline Space class#
Since we will be calling over and over the block-lines [5] and [6], we created a class called SplineSpace that you can import from psydac library.
Matrix profile#
Let’s check the profile of our matrix.
import matplotlib.pyplot as plt
plt.spy(stiffness)
As expected, it is a sparse matrix and it has a typical profile (band one).
Next step#
In fact, we are not done yet! The problem now, is that the data structure we are using for the matrix (here as a dense matrix) will consume too much memory, and is not taken into account the locality!
Since on each element, there are exactly \(p+1\) consecutive non-vanishing B-splines, we know that on each element there are at most \(2p+1\) non zeros entries (in every direction). This means that we will need to set entries as the following, in \(1D\):
M[i1, j1 - i1] += v_s
in \(2D\):
M[i1, i2, j1 - i1, j2 - i2] += v_s
and in \(3D\):
M[i1, i2, i3, j1 - i1, j2 - i2, j3 - i3] += v_s
Unfortunatly, this can not be represented as a numpy.ndarray, because of the negative indexing! Moreover, if our aim is to write a parallel code, other consideration must be taken into account.
More details will be found in the next section.
Exercises#
Exercise 1.#
Implement a 1D Poisson solver using non homogeneous Dirichlet boundary conditions.
Implement a 2D Poisson solver using homogeneous and non homogeneous Dirichlet boundary conditions.
Exercise 2.#
We consider the Heat equation in 1D
with homogeneous boundary conditions.
Write an explicit FEM solver. What do you notice about the dependency of the CFL with respect to the degree? what about the regularity?
Write an implicit FEM solver.
Exercise 3.#
We consider a unidirectional flow in the velocity/pressure formulation
where the unknown \(u_x\) is a function of \(y\) only.
We will consider \(\partial_x p = -2\) and \(g_x = 0.4\). The boundary conditions are given by \(u_x(y=0) = u_x(y=h) = 0\).
Write an explicit and implicit time schemes and solvers.
Find numerical CFL in the case of explicit time scheme.
Find the steady state.