How to add a new element type
To add a new finite element there's a few things we need to do. First, we may need to define an abstract type if the element type is not a currently supported abstract element topology. The currently supported abstract element topologies are
AbstractEdgeAbstractFaceAbstractQuadMyAbstractTriAbstractVolumeAbstractHexAbstractTet
We'll use a MyTri3 element as an example of how to add a new element type.
First, let's pretend MyAbstractTri doesn't exist yet. The first thing we need to do is define the following abstract type
julia> using ReferenceFiniteElements
julia> abstract type MyAbstractTri{PT, PD} <: ReferenceFiniteElements.AbstractFace{PT, PD} end
The two parameters PT and PD are the polynomial type and polynomial degree.
There are four loose interfaces associated with each element type
- The topology interace: this defines methods that describes basics of an element such as vertices, edges, etc.
- The dof interface: this defines methods describes where/how dofs are used on an element for a given interpolation type
- The quadrature interface: this defines methods that implement quadrature stencils
- The shape function interface: this implement interpolants on the element
Topology interface
function boundary_element end
function boundary_normals end # always returns 3 x num boundaries
function dimension end
function edge_vertices end
function face_vertices end
function num_boundaries end
function num_edges end
function num_faces end
function num_vertices_per_cell end
function vertex_coordinates end # always returns 3 x num_vertices_per_cellFor a face, the dimension, face_vertices, num_boundaries, and num_faces methods are already implemented since they are constant for any face type.
Now we define an abstract type for a triangle
julia> abstract type MyAbstractTri{PT, PD} <: ReferenceFiniteElements.AbstractFace{PT, PD} end
We can finish the topology interface on this abstract type with the following method implementations
julia> boundary_element(::MyAbstractTri{PT, PD}, ::Int) where {PT, PD} = Edge{PT, PD}(; shifted = true)
boundary_element (generic function with 1 method)
julia> boundary_normals(::MyAbstractTri) = [0. 1. / sqrt(2.) -1.; -1. 1. / sqrt(2.) 0.; 0. 0. 0.]
boundary_normals (generic function with 1 method)
julia> edge_vertices(::MyAbstractTri) = [1 2 3; 2 3 1]
edge_vertices (generic function with 1 method)
julia> num_edges(::MyAbstractTri) = 3
num_edges (generic function with 1 method)
julia> num_vertices_per_cell(::MyAbstractTri) = 3
num_vertices_per_cell (generic function with 1 method)
julia> vertex_coordinates(::MyAbstractTri) = [0. 1. 0.; 0. 0. 1.; 0. 0. 0.]
vertex_coordinates (generic function with 1 method)Dof interface
Now we define a struct for specific implementation. In this case the implementation is general. In the future we could have specific triangle implementations that break this pattern such as Tri7 or complex composite type elements. Here we'll define the MyTri struct.
julia> struct MyTri{PD, PT} <: MyAbstractTri{PD, PT} endQuadrature interface
Below is a simple implementation of the quadrature stencil for first and second order.
function cell_quadrature_points_and_weights(::MyAbstractTri, q_rule::GaussLobattoLegendre)
if cell_quadrature_degree(q_rule) == 1
ξs = Matrix{Float64}(undef, 2, 1)
ξs[:, 1] = [1. / 3., 1. / 3.]
ws = [0.5]
elseif cell_quadrature_degree(q_rule) == 2
ξs = Matrix{Float64}(undef, 2, 3)
ξs[:, 1] = [2. / 3., 1. / 6.]
ξs[:, 2] = [1. / 6., 2. / 3.]
ξs[:, 3] = [1. / 6., 1. / 6.]
ws = [1. / 6., 1. / 6., 1. / 6.]
else
@assert false
end
end
# output
cell_quadrature_points_and_weights (generic function with 1 method)Shape function interface
Now that we have a basic type to represent our element topology defined, we now need to implement our interpolation scheme. The methods needed for specific interpolation types include the following:
shape_function_valuesshape_funciton_gradientsshape_function_hessians
Below is an example for the MyTri3 element above
function shape_function_value(e::MyTri{Lagrange, PD}, X, ξ) where PD
Ns = [
1. - ξ[1] - ξ[2],
ξ[1],
ξ[2]
]
return Ns
end
# output
shape_function_value (generic function with 1 method)
function shape_function_gradient(e::MyTri{Lagrange, PD}, X, ξ) where PD
Ns = [
-1. -1.;
1. 0.;
0. 1.
]
return Ns
end
# output
shape_function_gradient (generic function with 1 method)
function shape_function_hessian(e::MyTri{Lagrange, PD}, X, ξ) where PD
Ns = zeros(3, 2, 2)
return Ns
end
# output
shape_function_hessian (generic function with 1 method)