forked from gridap/Gridap.jl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request gridap#1067 from gridap/pullbacks
Pullbacks
- Loading branch information
Showing
43 changed files
with
969 additions
and
340 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
|
||
# FE basis transformations | ||
|
||
## Notation | ||
|
||
Consider a reference polytope ``\hat{K}``, mapped to the physical space by a **geometrical map** ``F``, i.e. ``K = F(\hat{K})``. Consider also a linear function space on the reference polytope ``\hat{V}``, and a set of unisolvent degrees of freedom represented by moments in the dual space ``\hat{V}^*``. | ||
|
||
Throughout this document, we will use the following notation: | ||
|
||
- ``\varphi \in V`` is a **physical field** ``\varphi : K \rightarrow \mathbb{R}^k``. A basis of ``V`` is denoted by ``\Phi = \{\varphi\}``. | ||
- ``\hat{\varphi} \in \hat{V}`` is a **reference field** ``\hat{\varphi} : \hat{K} \rightarrow \mathbb{R}^k``. A basis of ``\hat{V}`` is denoted by ``\hat{\Phi} = \{\hat{\varphi}\}``. | ||
- ``\sigma \in V^*`` is a **physical moment** ``\sigma : V \rightarrow \mathbb{R}``. A basis of ``V^*`` is denoted by ``\Sigma = \{\sigma\}``. | ||
- ``\hat{\sigma} \in \hat{V}^*`` is a **reference moment** ``\hat{\sigma} : \hat{V} \rightarrow \mathbb{R}``. A basis of ``\hat{V}^*`` is denoted by ``\hat{\Sigma} = \{\hat{\sigma}\}``. | ||
|
||
## Pullbacks and Pushforwards | ||
|
||
We define a **pushforward** map as ``F^* : \hat{V} \rightarrow V``, mapping reference fields to physical fields. Given a pushforward ``F^*``, we define: | ||
|
||
- The **pullback** ``F_* : V^* \rightarrow \hat{V}^*``, mapping physical moments to reference moments. Its action on physical dofs is defined in terms of the pushforward map ``F^*`` as ``\hat{\sigma} = F_*(\sigma) := \sigma \circ F^*``. | ||
- The **inverse pushforward** ``(F^*)^{-1} : V \rightarrow \hat{V}``, mapping physical fields to reference fields. | ||
- The **inverse pullback** ``(F_*)^{-1} : \hat{V}^* \rightarrow V^*``, mapping reference moments to physical moments. Its action on reference dofs is defined in terms of the inverse pushforward map ``(F^*)^{-1}`` as ``\sigma = (F_*)^{-1}(\hat{\sigma}) := \hat{\sigma} \circ (F^*)^{-1}``. | ||
|
||
## Change of basis | ||
|
||
In many occasions, we will have that (as a basis) | ||
|
||
```math | ||
\hat{\Sigma} \neq F_*(\Sigma), \quad \text{and} \quad \Phi \neq F^*(\hat{\Phi}) | ||
``` | ||
|
||
To maintain conformity and proper scaling in these cases, we define cell-dependent invertible changes of basis ``P`` and ``M``, such that | ||
|
||
```math | ||
\hat{\Sigma} = P F_*(\Sigma), \quad \text{and} \quad \Phi = M F^*(\hat{\Phi}) | ||
``` | ||
|
||
An important result from [1, Theorem 3.1] is that ``P = M^T``. | ||
|
||
!!! details | ||
[1, Lemma 2.6]: A key ingredient is that given ``M`` a matrix we have ``\Sigma (M \Phi) = \Sigma (\Phi) M^T`` since | ||
```math | ||
[\Sigma (M \Phi)]_{ij} = \sigma_i (M_{jk} \varphi_k) = M_{jk} \sigma_i (\varphi_k) = [\Sigma (\Phi) M^T]_{ij} | ||
``` | ||
where we have used that moments are linear. | ||
|
||
We then have the following diagram: | ||
|
||
```math | ||
\hat{V}^* \xleftarrow{P} \hat{V}^* \xleftarrow{F_*} V^* \\ | ||
\hat{V} \xrightarrow{F^*} V \xrightarrow{P^T} V | ||
``` | ||
|
||
!!! details | ||
The above diagram is well defined, since we have | ||
```math | ||
\hat{\Sigma}(\hat{\Phi}) = P F_* (\Sigma)(F^{-*} (P^{-T} \Phi)) = P \Sigma (F^* (F^{-*} P^{-T} \Phi)) = P \Sigma (P^{-T} \Phi) = P \Sigma (\Phi) P^{-1} = Id \\ | ||
\Sigma(\Phi) = F_*^{-1}(P^{-1}\hat{\Sigma})(P^T F^*(\hat{\Phi})) = P^{-1} \hat{\Sigma} (F^{-*}(P^T F^*(\hat{\Phi}))) = P^{-1} \hat{\Sigma} (P^T \hat{\Phi}) = P^{-1} \hat{\Sigma}(\hat{\Phi}) P = Id | ||
``` | ||
|
||
From an implementation point of view, it is more natural to build ``P^{-1}`` and then retrieve all other matrices by transposition/inversion. | ||
|
||
## Interpolation | ||
|
||
In each cell ``K`` and for ``C_b^k(K)`` the space of functions defined on ``K`` with at least ``k`` bounded derivatives, we define the interpolation operator ``I_K : C_b^k(K) \rightarrow V`` as | ||
|
||
```math | ||
I_K(g) = \Sigma(g) \Phi \quad, \quad \Sigma(g) = P^{-1} \hat{\Sigma}(F^{-*}(g)) | ||
``` | ||
|
||
## Implementation notes | ||
|
||
!!! note | ||
In [2], Covariant and Contravariant Piola maps preserve exactly (without any sign change) the normal and tangential components of a vector field. | ||
I am quite sure that the discrepancy is coming from the fact that the geometrical information in the reference polytope is globally oriented. | ||
For instance, the normals ``n`` and ``\hat{n}`` both have the same orientation, i.e ``n = (||\hat{e}||/||e||) (det J) J^{-T} \hat{n}``. Therefore ``\hat{n}`` is not fully local. See [2, Equation 2.11]. | ||
In our case, we will be including the sign change in the transformation matrices, which will include all cell-and-dof-dependent information. | ||
|
||
## References | ||
|
||
[1] [Kirby 2017, A general approach to transforming finite elements.](https://arxiv.org/abs/1706.09017) | ||
|
||
[2] [Aznaran et al. 2021, Transformations for Piola-mapped elements.](https://arxiv.org/abs/2110.13224) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
""" | ||
""" | ||
struct FineToCoarseDofBasis{T,A,B,C} <: AbstractVector{T} | ||
dof_basis :: A | ||
rrule :: B | ||
child_ids :: C | ||
|
||
function FineToCoarseDofBasis(dof_basis::AbstractVector{T},rrule::RefinementRule) where {T<:Dof} | ||
nodes = get_nodes(dof_basis) | ||
child_ids = map(x -> x_to_cell(rrule,x),nodes) | ||
|
||
A = typeof(dof_basis) | ||
B = typeof(rrule) | ||
C = typeof(child_ids) | ||
new{T,A,B,C}(dof_basis,rrule,child_ids) | ||
end | ||
end | ||
|
||
Base.size(a::FineToCoarseDofBasis) = size(a.dof_basis) | ||
Base.axes(a::FineToCoarseDofBasis) = axes(a.dof_basis) | ||
Base.getindex(a::FineToCoarseDofBasis,i::Integer) = getindex(a.dof_basis,i) | ||
Base.IndexStyle(a::FineToCoarseDofBasis) = IndexStyle(a.dof_basis) | ||
|
||
ReferenceFEs.get_nodes(a::FineToCoarseDofBasis) = get_nodes(a.dof_basis) | ||
|
||
# Default behaviour | ||
Arrays.return_cache(b::FineToCoarseDofBasis,field) = return_cache(b.dof_basis,field) | ||
Arrays.evaluate!(cache,b::FineToCoarseDofBasis,field) = evaluate!(cache,b.dof_basis,field) | ||
|
||
# Spetialized behaviour | ||
function Arrays.return_cache(s::FineToCoarseDofBasis{T,<:LagrangianDofBasis},field::FineToCoarseField) where T | ||
b = s.dof_basis | ||
cf = return_cache(field,b.nodes,s.child_ids) | ||
vals = evaluate!(cf,field,b.nodes,s.child_ids) | ||
ndofs = length(b.dof_to_node) | ||
r = ReferenceFEs._lagr_dof_cache(vals,ndofs) | ||
c = CachedArray(r) | ||
return (c, cf) | ||
end | ||
|
||
function Arrays.evaluate!(cache,s::FineToCoarseDofBasis{T,<:LagrangianDofBasis},field::FineToCoarseField) where T | ||
c, cf = cache | ||
b = s.dof_basis | ||
vals = evaluate!(cf,field,b.nodes,s.child_ids) | ||
ndofs = length(b.dof_to_node) | ||
T2 = eltype(vals) | ||
ncomps = num_indep_components(T2) | ||
@check ncomps == num_indep_components(eltype(b.node_and_comp_to_dof)) """\n | ||
Unable to evaluate LagrangianDofBasis. The number of components of the | ||
given Field does not match with the LagrangianDofBasis. | ||
If you are trying to interpolate a function on a FESpace make sure that | ||
both objects have the same value type. | ||
For instance, trying to interpolate a vector-valued function on a scalar-valued FE space | ||
would raise this error. | ||
""" | ||
ReferenceFEs._evaluate_lagr_dof!(c,vals,b.node_and_comp_to_dof,ndofs,ncomps) | ||
end | ||
|
||
function Arrays.return_cache(s::FineToCoarseDofBasis{T,<:MomentBasedDofBasis},field::FineToCoarseField) where T | ||
b = s.dof_basis | ||
cf = return_cache(field,b.nodes,s.child_ids) | ||
vals = evaluate!(cf,field,b.nodes,s.child_ids) | ||
ndofs = num_dofs(b) | ||
r = ReferenceFEs._moment_dof_basis_cache(vals,ndofs) | ||
c = CachedArray(r) | ||
return (c, cf) | ||
end | ||
|
||
function Arrays.evaluate!(cache,s::FineToCoarseDofBasis{T,<:MomentBasedDofBasis},field::FineToCoarseField) where T | ||
c, cf = cache | ||
b = s.dof_basis | ||
vals = evaluate!(cf,field,b.nodes,s.child_ids) | ||
dofs = c.array | ||
ReferenceFEs._eval_moment_dof_basis!(dofs,vals,b) | ||
dofs | ||
end | ||
|
||
|
||
""" | ||
Wrapper for a ReferenceFE which is specialised for | ||
efficiently evaluating FineToCoarseFields. | ||
""" | ||
struct FineToCoarseRefFE{T,D,A} <: ReferenceFE{D} | ||
reffe :: T | ||
dof_basis :: A | ||
|
||
function FineToCoarseRefFE(reffe::ReferenceFE{D},dof_basis::FineToCoarseDofBasis) where D | ||
T = typeof(reffe) | ||
A = typeof(dof_basis) | ||
new{T,D,A}(reffe,dof_basis) | ||
end | ||
end | ||
|
||
ReferenceFEs.num_dofs(reffe::FineToCoarseRefFE) = num_dofs(reffe.reffe) | ||
ReferenceFEs.get_polytope(reffe::FineToCoarseRefFE) = get_polytope(reffe.reffe) | ||
ReferenceFEs.get_prebasis(reffe::FineToCoarseRefFE) = get_prebasis(reffe.reffe) | ||
ReferenceFEs.get_dof_basis(reffe::FineToCoarseRefFE) = reffe.dof_basis | ||
ReferenceFEs.Conformity(reffe::FineToCoarseRefFE) = Conformity(reffe.reffe) | ||
ReferenceFEs.get_face_dofs(reffe::FineToCoarseRefFE) = get_face_dofs(reffe.reffe) | ||
ReferenceFEs.get_shapefuns(reffe::FineToCoarseRefFE) = get_shapefuns(reffe.reffe) | ||
ReferenceFEs.get_metadata(reffe::FineToCoarseRefFE) = get_metadata(reffe.reffe) | ||
ReferenceFEs.get_orders(reffe::FineToCoarseRefFE) = get_orders(reffe.reffe) | ||
ReferenceFEs.get_order(reffe::FineToCoarseRefFE) = get_order(reffe.reffe) | ||
|
||
ReferenceFEs.Conformity(reffe::FineToCoarseRefFE,sym::Symbol) = Conformity(reffe.reffe,sym) | ||
ReferenceFEs.get_face_own_dofs(reffe::FineToCoarseRefFE,conf::Conformity) = get_face_own_dofs(reffe.reffe,conf) | ||
|
||
|
||
function ReferenceFEs.ReferenceFE(p::Polytope,rrule::RefinementRule,name::ReferenceFEName,order) | ||
FineToCoarseRefFE(p,rrule,name,Float64,order) | ||
end | ||
|
||
function ReferenceFEs.ReferenceFE(p::Polytope,rrule::RefinementRule,name::ReferenceFEName,::Type{T},order) where T | ||
FineToCoarseRefFE(p,rrule,name,T,order) | ||
end | ||
|
||
function FineToCoarseRefFE(p::Polytope,rrule::RefinementRule,name::ReferenceFEName,::Type{T},order) where T | ||
@check p == get_polytope(rrule) | ||
reffe = ReferenceFE(p,name,T,order) | ||
dof_basis = FineToCoarseDofBasis(get_dof_basis(reffe),rrule) | ||
return FineToCoarseRefFE(reffe,dof_basis) | ||
end | ||
|
||
# FESpaces constructors | ||
|
||
function FESpaces.TestFESpace(model::DiscreteModel,rrules::AbstractVector{<:RefinementRule},reffe::Tuple{<:ReferenceFEName,Any,Any};kwargs...) | ||
@check num_cells(model) == length(rrules) | ||
@check all(CompressedArray(get_polytopes(model),get_cell_type(model)) .== lazy_map(get_polytope,rrules)) | ||
basis, reffe_args, reffe_kwargs = reffe | ||
reffes = lazy_map(rr -> ReferenceFE(get_polytope(rr),rr,basis,reffe_args...;reffe_kwargs...),rrules) | ||
return TestFESpace(model,reffes;kwargs...) | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.