A package for computing unassembled matrices appearing in
This package computes matrices of the following form.
where
The basis functions we use here are
where
and
To compute the matrix
where
where
In order to guarantee that the matrices FastGaussQuadrature at each iteration. We iteratively calculate the matrices with an increasingly large number of quadrature points and iterate until the result is constant up to absolute and relative tolerances. The inputs used to control this process are described in the section on usage below. For polynomial kernels we can deduce the number of quadrature points for exact results, avoiding the need for the iterative calculation in the pure polynomial case.
To create these matrices, the user must supply the key grid
information for the element of interest, i.e., the set
ElementCoordinates. We use FastGaussQuadrature
to make a set of reference nodes, but any set of nodes with good
interpolation properties would be adequate.
using FastGaussQuadrature: gausslobatto
using FiniteElementMatrices
ngrid = 5 # change this to change the order of the higher-order FEM scheme
x, w = gausslobatto(ngrid)
scale = 1.0 # change these to match the size and position of elements in the problem
shift = 0.0
# the coordinate information
coordinate = ElementCoordinates(x,scale,shift)
Once an instance of ElementCoordinates is available,
we can compute the higher-order finite element matrices
as follows. We use the enums
lagrange_x, and d_lagrange_dx and the integer p to choose the form of the desired matrix. For a mass matrix, with
Jacobian
using FiniteElementMatrices
coordinate = ElementCoordinates(x,scale,shift)
M = finite_element_matrix(lagrange_x,lagrange_x,0,coordinate)
Note that the order in which the @enum Lagrange polynomials are given corresponds to the indices of M. E.g., for a matrix describing a first derivative
l_i = lagrange_x
l_j = d_lagrange_dx
P = finite_element_matrix(l_i,l_j,0,coordinate)
the number P[i,j] corresponds to the matrix element
For a stiffness matrix describing a second derivative, we could use the following.
K = -finite_element_matrix(d_lagrange_dx,d_lagrange_dx,0,coordinate)
The second derivative of a function
with
For a system with Jacobian
coordinate = ElementCoordinates(x,scale,shift)
M = finite_element_matrix(lagrange_x,lagrange_x,1,coordinate)
K = -finite_element_matrix(d_lagrange_dx,d_lagrange_dx,1,coordinate)
i.e.,
Finally, we can form a nonlinear stiffness matrix by inserting an extra enum argument, as follows.
N = finite_element_matrix(lagrange_x,lagrange_x,lagrange_x,1,coordinate)
To specify an arbitrary kernel function
coordinate = ElementCoordinates(x,scale,shift)
M = finite_element_matrix(lagrange_x,lagrange_x,coordinate,
kernel_function=(v -> rho(v)),additional_quadrature_points=n,
quadrature_increment=m,
max_iterations=q, atol=atol, rtol=rtol)
where we specify some function rho(v) to be the kernel, and we specify that we n additional quadrature
points should be used in addition to the number assumed from the size of the reference grid x. If q is greater than zero, for each iteration we proceed to calculate the matrix again with a further additional m quadrature points, until the matrix is calculated to the specified absolute tolerance atol and relative tolerance rtol, or we call error() with an appropriate message for the user. Note that rho(v) should be specified in terms of the physical coordinate including the scale and shift factors v = scale * x + shift, not in terms of the reference coordinate x.
It is possible to write down weak forms where the multi-dimensional integrals do not separate into a product of one-dimensional integrals, even for a tensor-product basis. This occurs where the kernel is not a separable function, e.g., for the matrix
where
The syntax that we use to create matrices in two coordinates is as follows
l_i = lagrange_x
l_j = lagrange_x
l_k = d_lagrange_dx
l_n = d_lagrange_dx
K_2D = finite_element_matrix(l_i,l_j,coordinate_x1,
l_k,l_n,coordinate_x2;
kernel_function=((x1,x2)->rho(x1,x2)),
max_iterations=10, quadrature_increment=10,
atol=1.0e-13, rtol=1.0e-13)
where we have two coordinates, x1 (with polynomials l_i and l_j) and x2 (with polynomials l_k and l_n). Note that the @enum variables used to select the Lagrange polynomials are the same between the two coordinates.
The matrix created above is indexed in the order K_2D[i,k,j,n], indexing over the first polynomial l_i of x1, then the first polynomial l_k of x2, then the second polynomial l_j of x1 with the final index representing the polynomial l_n of x2. The same adaptive quadrature keyword arguments are used as in the 1D case.
For some function inputs GLSpecifiedLimits(x_min, x_max) which can limit the range of integration where necessary without introducing the need to define x_min and x_max should be supplied in the physical coordinate normalisation for the coordinate coordinate, rather than in the reference normalisation on the reference domain [x_min, x_max], we compute the integral
For the integral
the corresponding syntax for the matrix element
finite_element_matrix(lagrange_x,lagrange_x,coordinate,
kernel_function=(x -> rho(x)),
quadrature_option=GLSpecifiedLimits(x_min,x_max))
For matrices of integrals in two dimensions, we use a similar syntax, as follows.
finite_element_matrix(lagrange_x,lagrange_x,x1_coordinate,
lagrange_x,lagrange_x,x2_coordinate,
kernel_function=((x1,x2)->rho(x1,x2)),
quadrature_option_x1=GLSpecifiedLimits(x1_min,x1_max),
quadrature_option_x2=GLSpecifiedLimits(x2_min,x2_max))
The default quadrature options for integrals in the range
quadrature_option=Default1DQuadrature()
quadrature_option_x1=Default1DQuadrature()
quadrature_option_x2=Default1DQuadrature()
There are several examples of taking first and second derivatives
in the tests of this package FiniteElementMatrices/test/runtests.jl, as well as more complicated usages in the package
implementing Fokker-Planck Coulomb collisions.