Function spaces

Generic

Bcube.AbstractFunctionSpaceTypeType

Abstract structure for the different types of function space, for instance the Lagrange function space, the Taylor function space etc.

Devs notes

All subtypes should implement the following functions:

  • get_ndofs(::AbstractFunctionSpace, ::AbstractShape)
  • _scalar_shape_functions(::AbstractFunctionSpace, ::AbstractShape, ξ)
  • idof_by_vertex(::AbstractFunctionSpace, ::AbstractShape)
  • idof_by_edge(::AbstractFunctionSpace, ::AbstractShape)
  • idof_by_edge_with_bounds(::AbstractFunctionSpace, ::AbstractShape)
  • idof_by_face(::AbstractFunctionSpace, ::AbstractShape)
  • idof_by_face_with_bounds(::AbstractFunctionSpace, ::AbstractShape)
Bcube.FunctionSpaceMethod
FunctionSpace(fstype::Symbol, degree::Integer)
FunctionSpace(fstype::AbstractFunctionSpaceType, degree::Integer)

Build a FunctionSpace of the designated FunctionSpaceType and degree.

Examples

julia> FunctionSpace(:Lagrange, 2)
FunctionSpace{Bcube.Lagrange{:Uniform}, 2}()
Bcube.basis_functions_styleMethod
basis_functions_style(fs::AbstractFunctionSpace)

Return the style (modal or nodal) corresponding to the basis functions of the 'fs'.

Bcube.get_coordsMethod
get_coords(fs::AbstractFunctionSpace,::AbstractShape)

Return node coordinates in the reference space for associated function space and shape.

Bcube.get_degreeMethod
get_degree(::AbstractFunctionSpace{type, degree}) where{type, degree}

Return the degree associated to the AbstractFunctionSpace.

Bcube.get_ndofsMethod
get_ndofs(fs::AbstractFunctionSpace, shape::AbstractShape)

Number of dofs associated to the given interpolation.

Bcube.get_typeMethod
get_type(::AbstractFunctionSpace{type})

Getter for the type of the AbstractFunctionSpace

Bcube.idof_by_edgeMethod
idof_by_edge(::AbstractFunctionSpace, ::AbstractShape)

Return the local indices of the dofs lying on each edge of the Shape.

Dofs lying on the edge vertices are excluded.

The result is a Tuple of arrays of integers. Arrays maybe be empty. See Lagrange interpolation for simple examples.

Bcube.idof_by_edge_with_boundsMethod
idof_by_edge_with_bounds(::AbstractFunctionSpace, ::AbstractShape)

Return the local indices of the dofs lying on each edge of the Shape.

Dofs lying on the edge vertices are included.

The result is a Tuple of arrays of integers. Arrays maybe be empty. See Lagrange interpolation for simple examples.

Bcube.idof_by_faceMethod
idof_by_face(::AbstractFunctionSpace, ::AbstractShape)

Return the local indices of the dofs lying on each face of the Shape.

Dofs lying on the face edges are excluded, only "face-interior" dofs are considered.

The result is a Tuple of arrays of integers. Arrays maybe be empty. See Lagrange interpolation for simple examples.

Bcube.idof_by_face_with_boundsMethod
idof_by_face_with_bounds(::AbstractFunctionSpace, ::AbstractShape)

Return the local indices of the dofs lying on each face of the Shape.

Dofs lying on the face edges are included

The result is a Tuple of arrays of integers. Arrays maybe be empty. See Lagrange interpolation for simple examples.

Bcube.idof_by_vertexMethod
idof_by_vertex(::AbstractFunctionSpace, ::AbstractShape)

Return the local indices of the dofs lying on each vertex of the Shape.

Beware that we are talking about the Shape, not the EntityType. So 'interior' vertices of the EntityType are not taken into account for instance. See Lagrange interpolation for simple examples.

Bcube.shape_functionsMethod
shape_functions(::AbstractFunctionSpace, ::Val{N}, shape::AbstractShape, ξ) where N
shape_functions(::AbstractFunctionSpace, shape::AbstractShape, ξ)

Return the list of shape functions corresponding to a FunctionSpace and a Shape. N is the size of the finite element space (default: N=1 if the argument is not provided).

The result is a vector of all the shape functions evaluated at position ξ, and not a tuple of the different shape functions. This choice is optimal for performance.

Note : λ = ξ -> shape_functions(fs, shape, ξ); λ(ξ)[i] is faster than λ =shape_functions(fs, shape); λ[i](ξ)

Implementation

Default version, should be overriden for each concrete FunctionSpace.

Bcube.shape_functions_vecMethod
shape_functions_vec(fs::AbstractFunctionSpace, ::Val{N}, shape::AbstractShape, ξ) where {N}

Return all the shape functions of FunctionSpace on a Shape evaluated in ξ as a vector.

N is the the size (number of components) of the finite element space.


shape_functions_vec(fs::AbstractFunctionSpace{T,D}, n::Val{N}, shape::AbstractShape) where {T,D, N}

The shape functions are returned as a vector of functions.

Implementation

This is implementation is not always valid, but it is for Lagrange and Taylor spaces (the only two spaces available up to 20/01/23).

Bcube.∂λξ_∂ξFunction
∂λξ_∂ξ(::AbstractFunctionSpace, ::Val{N}, shape::AbstractShape) where N

Gradient, with respect to the reference coordinate system, of shape functions for any function space. The result is an array whose elements are the gradient of each shape functions. N is the size of the finite element space.

Implementation

Default version using automatic differentiation. Specialize to increase performance.

Lagrange

Bcube._scalar_shape_functionsMethod
shape_functions(::FunctionSpace{<:Lagrange, 1}, ::Tetra, ξ)

Shape functions for Tetra Lagrange element of degree 1 in a 3D space.

\[\hat{\lambda}_1(\xi, \eta, \zeta) = (1 - \xi - \eta - \zeta) \hspace{1cm} \hat{\lambda}_2(\xi, \eta, \zeta) = \xi \hspace{1cm} \hat{\lambda}_3(\xi, \eta, \zeta) = \eta \hspace{1cm} \hat{\lambda}_5(\xi, \eta, \zeta) = \zeta \hspace{1cm}\]

Bcube.shape_functionsMethod
shape_functions(::FunctionSpace{<:Lagrange}, :: Val{N}, ::AbstractShape, ξ) where {N}

Implementation

For N > 1, the default version consists in "replicating" the shape functions. If shape_functions returns the vector [λ₁; λ₂; λ₃], and if the FESpace is of size 2, then this default behaviour consists in returning the matrix [λ₁ 0; λ₂ 0; λ₃ 0; 0 λ₁; 0 λ₂; 0 λ₃].

Triangle

Order 1

\[\hat{\lambda}_1(\xi, \eta) = 1 - \xi - \eta \hspace{1cm} \hat{\lambda}_2(\xi, \eta) = \xi \hspace{1cm} \hat{\lambda}_3(\xi, \eta) = \eta\]

Order 2

\[\begin{aligned} & \hat{\lambda}_1(\xi, \eta) = (1 - \xi - \eta)(1 - 2 \xi - 2 \eta) \\ & \hat{\lambda}_2(\xi, \eta) = \xi (2\xi - 1) \\ & \hat{\lambda}_3(\xi, \eta) = \eta (2\eta - 1) \\ & \hat{\lambda}_{12}(\xi, \eta) = 4 \xi (1 - \xi - \eta) \\ & \hat{\lambda}_{23}(\xi, \eta) = 4 \xi \eta \\ & \hat{\lambda}_{31}(\xi, \eta) = 4 \eta (1 - \xi - \eta) \end{aligned}\]

Prism

Order 1

\[\begin{aligned} \hat{\lambda}_1(\xi, \eta, \zeta) = (1 - \xi - \eta)(1 - \zeta)/2 \hspace{1cm} \hat{\lambda}_2(\xi, \eta, \zeta) = \xi (1 - \zeta)/2 \hspace{1cm} \hat{\lambda}_3(\xi, \eta, \zeta) = \eta (1 - \zeta)/2 \hspace{1cm} \hat{\lambda}_5(\xi, \eta, \zeta) = (1 - \xi - \eta)(1 + \zeta)/2 \hspace{1cm} \hat{\lambda}_6(\xi, \eta, \zeta) = \xi (1 + \zeta)/2 \hspace{1cm} \hat{\lambda}_7(\xi, \eta, \zeta) = \eta (1 + \zeta)/2 \hspace{1cm} \end{aligned}\]

Bcube.shape_functions_symbolicMethod
shape_functions_symbolic(fs::FunctionSpace{<:Lagrange, D}, ::Shape, ξ) where {D, Shape<:Line}
∂λξ_∂ξ_symbolic(fs::FunctionSpace{<:Lagrange, D}, ::Shape, ξ) where {D, Shape<:Line}

Implementation

Based on Symbolic.jl. First tests show that this version is slower than the implementation based on meta when D is greater. Further investigations are needed to understand this behavior.

shape_functions_symbolic uses a "generated" function named _shape_functions_symbolic. The core of the generated function is an Expression that is created by __shape_functions_symbolic. This latter function uses the Symbolics package and the lagrange polynomials (defined in _lagrange_poly).

Bcube.∂λξ_∂ξMethod
∂λξ_∂ξ(::FunctionSpace{<:Lagrange}, ::Val{1}, ::AbstractShape, ξ)

Triangle

Order 0

\[\nabla \hat{\lambda}(\xi, \eta) = \begin{pmatrix} 0 \\ 0 \end{pmatrix}\]

Order 1

\[\begin{aligned} & \nabla \hat{\lambda}_1(\xi, \eta) = \begin{pmatrix} -1 \\ -1 \end{pmatrix} \\ & \nabla \hat{\lambda}_2(\xi, \eta) = \begin{pmatrix} 1 \\ 0 \end{pmatrix} \\ & \nabla \hat{\lambda}_3(\xi, \eta) = \begin{pmatrix} 0 \\ 1 \end{pmatrix} \\ \end{aligned}\]

Order 2

\[\begin{aligned} & \nabla \hat{\lambda}_1(\xi, \eta) = \begin{pmatrix} -3 + 4 (\xi + \eta) \\ -3 + 4 (\xi + \eta) \end{pmatrix} \\ & \nabla \hat{\lambda}_2(\xi, \eta) = \begin{pmatrix} -1 + 4 \xi \\ 0 \end{pmatrix} \\ & \nabla \hat{\lambda}_3(\xi, \eta) = \begin{pmatrix} 0 \\ -1 + 4 \eta \end{pmatrix} \\ & \nabla \hat{\lambda}_{12}(\xi, \eta) = 4 \begin{pmatrix} 1 - 2 \xi - \eta \\ - \xi \end{pmatrix} \\ & \nabla \hat{\lambda}_{23}(\xi, \eta) = 4 \begin{pmatrix} \eta \\ \xi \end{pmatrix} \\ & \nabla \hat{\lambda}_{31}(\xi, \eta) = 4 \begin{pmatrix} - \eta \\ 1 - 2 \eta - \xi \end{pmatrix} \\ \end{aligned}\]

Square

Order 0

\[\nabla \hat{\lambda}(\xi, \eta) = \begin{pmatrix} 0 \\ 0 \end{pmatrix}\]

Taylor

The Taylor function space corresponds to a function space where functions are approximated by a Taylor series expansion of order $n$ in each cell:

\[ \forall x \in \Omega_i,~g(x) = g(x_0) + (x - x_0) g'(x_0) + o(x)\]

where $x_0$ is the cell center.

Note that a Taylor-P0 is strictly equivalent to a 1st-order Finite Volume discretization (beware that "order" can have different meaning depending on whether one refers to the order of the function space basis or the order of the discretization method).

Recall that any function space implies that any function $g$ is interpolated by $g(x) = \sum g_i \lambda_i(x)$ where $\lambda_i$ are the shape functions. For a Taylor expansion, the definition of $\lambda_i$ is not unique. For instance for the Taylor expansion of order $1$ on a 1D line above, we may be tempted to set $\lambda_1(x) = 1$ and $\lambda_2(x) = (x - x_0)$. If you do so, what are the corresponding shape functions in the reference element, the $\hat{\lambda_i}$? We immediately recover $\hat{\lambda_1}(\hat{x}) = 1$. For $\hat{\lambda_2}$:

\[ \hat{\lambda_2}(\hat{x}) = (\lambda \circ F)(\hat{x}) = (x \rightarrow x - x_0) \circ (\hat{x} \rightarrow \frac{x_r - x_l}{2} \hat{x} + \frac{x_r + x_l}{2}) = \frac{x_r - x_l}{2} \hat{x}\]

So if you set $\lambda_2(x) = (x - x_0)$ then $\hat{\lambda_2}$ depends on the element length ($\Delta x = x_r-x_l$), which is pointless. So $\lambda_2$ must be proportional to the element length to obtain a universal definition for $\hat{\lambda_2}$. For instance, we may choose $\lambda_2(x) = (x - x_0) / \Delta x$, leading to $\hat{\lambda_2}(\hat{x}) = \hat{x} / 2$. But we could have chosen an other element length multiple.

Don't forget that choosing $\lambda_2(x) = (x - x_0) / \Delta x$ leads to $g(x) = g(x_0) + \frac{x - x_0}{\Delta x} g'(x_0) Δx$ hence $g_2 = g'(x_0) Δx$ in the interpolation.

Bcube.shape_functionsMethod
shape_functions(::FunctionSpace{<:Taylor}, ::AbstractShape, ξ)

Implementation

For N > 1, the default version consists in "replicating" the shape functions. If shape_functions returns the vector [λ₁; λ₂; λ₃], and if the FESpace is of size 2, then this default behaviour consists in returning the matrix [λ₁ 0; λ₂ 0; λ₃ 0; 0 λ₁; 0 λ₂; 0 λ₃].

Any shape, order 0

$\hat{\lambda}(\xi) = 1$

Line

Order 1

\[\hat{\lambda}_1(\xi) = 1 \hspace{1cm} \hat{\lambda}_1(\xi) = \frac{\xi}{2}\]

Square

Order 1

\[\begin{aligned} & \hat{\lambda}_1(\xi, \eta) = 0 \\ & \hat{\lambda}_2(\xi, \eta) = \frac{\xi}{2} \\ & \hat{\lambda}_3(\xi, \eta) = \frac{\eta}{2} \end{aligned}\]

Bcube.∂λξ_∂ξMethod
∂λξ_∂ξ(::FunctionSpace{<:Taylor}, ::Val{1}, ::AbstractShape, ξ)

Line

Order 0

$\nabla \hat{\lambda}(\xi) = 0$

Order 1

\[\nabla \hat{\lambda}_1(\xi) = 0 \hspace{1cm} \nabla \hat{\lambda}_1(\xi) = \frac{1}{2}\]

Square

Order 0

\[\hat{\lambda}_1(\xi, \eta) = \begin{pmatrix} 0 \\ 0 \end{pmatrix}\]

Order 1

\[\begin{aligned} & \nabla \hat{\lambda}_1(\xi, \eta) = \begin{pmatrix} 0 \\ 0 \end{pmatrix} \\ & \nabla \hat{\lambda}_2(\xi, \eta) = \begin{pmatrix} \frac{1}{2} \\ 0 \end{pmatrix} \\ & \nabla \hat{\lambda}_3(\xi, \eta) = \begin{pmatrix} 0 \\ \frac{1}{2} \end{pmatrix} \end{aligned}\]