Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pullbacks #1067

Merged
merged 31 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8cc8918
AffineMap is now AffineField. Introduced new AffineMap and ConstantMap.
JordiManyer Oct 29, 2024
0288974
Minor fix
JordiManyer Oct 29, 2024
dfd7af5
Optimizations for lazy_map(evaluate,lazy_map(Reindex(f),ids),x)
JordiManyer Oct 30, 2024
6fb3aa6
Merge branch 'master' of github.com:gridap/Gridap.jl into optimisations
JordiManyer Nov 8, 2024
711722f
Ported Piola maps to the Pullback structure
JordiManyer Dec 12, 2024
b5ab282
Pullback API working
JordiManyer Dec 12, 2024
ea4e350
Added MappedDofBasis
JordiManyer Dec 12, 2024
74cfd29
Deprecated old machinery in favor of Pushforwards
JordiManyer Dec 13, 2024
f933240
Minor
JordiManyer Dec 13, 2024
9144878
Saved progress.
JordiManyer Dec 13, 2024
c55b95b
Merge branch 'master' of github.com:gridap/Gridap.jl into optimisations
JordiManyer Dec 14, 2024
509830b
Reverted some changes
JordiManyer Dec 14, 2024
e279910
Update NEWS
JordiManyer Dec 15, 2024
17a4f2e
Merge pull request #1043 from gridap/optimisations
JordiManyer Dec 15, 2024
ab452bb
Expanded ConstantFESpaces to work on sub-triangulations
JordiManyer Dec 16, 2024
113d30c
Updated docs and NEWS
JordiManyer Dec 16, 2024
04edf7e
Merge pull request #1069 from gridap/constant-fespaces
JordiManyer Dec 16, 2024
e618113
Added documentation on pullbacks
JordiManyer Dec 18, 2024
73909ac
Merge branch 'master' of github.com:gridap/Gridap.jl into pullbacks
JordiManyer Dec 18, 2024
2178276
Saved progress
JordiManyer Dec 18, 2024
e5d4df6
More notes on pullbacks
JordiManyer Dec 19, 2024
4823ac2
Minor
JordiManyer Dec 19, 2024
4a2cd6b
Started adding change of basis
JordiManyer Jan 2, 2025
6f50ebb
Minor bugfixes
JordiManyer Jan 2, 2025
7d40d1b
Minor
JordiManyer Jan 2, 2025
7b6bfb0
Minor
JordiManyer Jan 2, 2025
00620d9
Minor
JordiManyer Jan 2, 2025
d8ce683
Added deprecated code
JordiManyer Jan 2, 2025
b3002db
Optimised linear_combination for Diagonal matrices
JordiManyer Jan 2, 2025
b741a9a
Minor
JordiManyer Jan 2, 2025
ba8261a
HHJ reference FEs
JordiManyer Jan 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Added AMR-related methods `mark` and `estimate` to `Adaptivity` module. Implemented Dorfler marking strategy. Since PR[#1063](https://github.com/gridap/Gridap.jl/pull/1063).

### Changed

- Low level optimisations to reduce allocations. `AffineMap` renamed to `AffineField`. New `AffineMap <: Map`, doing the same as `AffineField` without struct allocation. New `ConstantMap <: Map`, doing the same as `ConstantField` without struct allocation. Since PR[#1043](https://github.com/gridap/Gridap.jl/pull/1043).
- `ConstantFESpaces` can now be built on triangulations. Since PR[#1069](https://github.com/gridap/Gridap.jl/pull/1069)

## [0.18.8] - 2024-12-2

### Added
Expand All @@ -25,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed issue where barycentric refinement rule in 3D would not produce oriented meshes. Since PR[#1055](https://github.com/gridap/Gridap.jl/pull/1055).

### Changed

- Optimized MonomialBasis low-level functions. Since PR[#1059](https://github.com/gridap/Gridap.jl/pull/1059).

## [0.18.7] - 2024-10-8
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pages = [
"Gridap.Adaptivity" => "Adaptivity.md",
"Developper notes" => Any[
"dev-notes/block-assemblers.md",
"dev-notes/pullbacks.md",
],
]

Expand Down
2 changes: 0 additions & 2 deletions docs/src/Adaptivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,4 @@ When a cell is refined, we need to be able to evaluate the fields defined on the

```@docs
FineToCoarseField
FineToCoarseDofBasis
FineToCoarseRefFE
```
71 changes: 48 additions & 23 deletions docs/src/ODEs.md

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion docs/src/ReferenceFEs.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,18 @@ Pages = ["LagrangianRefFEs.jl","LagrangianDofBases.jl","SerendipityRefFEs.jl",

### Moment-Based ReferenceFEs

#### Framework

```@autodocs
Modules = [ReferenceFEs,]
Order = [:type, :constant, :macro, :function]
Pages = ["MomentBasedReferenceFEs.jl","Pullbacks.jl"]
```

#### Available Moment-Based ReferenceFEs

```@autodocs
Modules = [ReferenceFEs,]
Order = [:type, :constant, :macro, :function]
Pages = ["MomentBasedReferenceFEs.jl","RaviartThomasRefFEs.jl","NedelecRefFEs.jl","BDMRefFEs.jl","CRRefFEs.jl"]
Pages = ["RaviartThomasRefFEs.jl","NedelecRefFEs.jl","BDMRefFEs.jl","CRRefFEs.jl"]
```
82 changes: 82 additions & 0 deletions docs/src/dev-notes/pullbacks.md
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)
2 changes: 1 addition & 1 deletion src/Adaptivity/RefinementRules.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ end
# - The reason why we are saving both the cell maps and the inverse cell maps is to avoid recomputing
# them when needed. This is needed for performance when the RefinementRule is used for MacroFEs.
# Also, in the case the ref_grid comes from a CartesianGrid, we save the cell maps as
# AffineMaps, which are more efficient than the default linear_combinations.
# AffineFields, which are more efficient than the default linear_combinations.
# - We cannot parametrise the RefinementRule by all it's fields, because we will have different types of
# RefinementRules in a single mesh. It's the same reason why we don't parametrise the ReferenceFE type.

Expand Down
132 changes: 132 additions & 0 deletions src/Adaptivity/deprecated/FineToCoarseReferenceFEs.jl
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
3 changes: 1 addition & 2 deletions src/Arrays/Arrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ export testargs
export inverse_map

export Broadcasting

export Operation

export InverseMap

# LazyArray

Expand Down
13 changes: 13 additions & 0 deletions src/Arrays/Maps.jl
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,16 @@ function inverse_map(f)
Function inverse_map is not implemented yet for objects of type $(typeof(f))
"""
end

struct InverseMap{F} <: Map
original::F
end

function evaluate!(cache,k::InverseMap,args...)
@notimplemented """\n
The inverse evaluation is not implemented yet for maps of type $(typeof(k.original))
"""
end

inverse_map(k::Map) = InverseMap(k)
inverse_map(k::InverseMap) = k.original
62 changes: 38 additions & 24 deletions src/FESpaces/ConstantFESpaces.jl
Original file line number Diff line number Diff line change
@@ -1,49 +1,63 @@

"""
struct ConstantFESpace <: SingleFieldFESpace
# private fields
end
struct ConstantFESpace <: SingleFieldFESpace
# private fields
end

ConstantFESpace(model::DiscreteModel; vector_type=Vector{Float64}, field_type=Float64)
ConstantFESpace(trian::Triangulation; vector_type=Vector{Float64}, field_type=Float64)

FESpace that is constant over the provided model/triangulation. Typically used as
lagrange multipliers. The kwargs `vector_type` and `field_type` are used to specify the
types of the dof-vector and dof-value respectively.
"""
struct ConstantFESpace{V,T,A,B,C} <: SingleFieldFESpace
model::DiscreteModel
trian::Triangulation
cell_basis::A
cell_dof_basis::B
cell_dof_ids::C

function ConstantFESpace(model;
vector_type::Type{V}=Vector{Float64},
field_type::Type{T}=Float64) where {V,T}
function setup_cell_reffe(model::DiscreteModel,
reffe::Tuple{<:ReferenceFEName,Any,Any}; kwargs...)
basis, reffe_args,reffe_kwargs = reffe
cell_reffe = ReferenceFE(model,basis,reffe_args...;reffe_kwargs...)
end
function ConstantFESpace(
model::DiscreteModel{Dc},
trian::Triangulation{Dc};
vector_type::Type{V}=Vector{Float64},
field_type::Type{T}=Float64
) where {Dc,V,T}
@assert num_cells(model) == num_cells(trian)

reffe = ReferenceFE(lagrangian,T,0)
cell_reffe = setup_cell_reffe(model,reffe)
basis, reffe_args, reffe_kwargs = ReferenceFE(lagrangian,T,0)
cell_reffe = ReferenceFE(model,basis,reffe_args...;reffe_kwargs...)
cell_basis_array = lazy_map(get_shapefuns,cell_reffe)

cell_basis = SingleFieldFEBasis(
cell_basis_array,
Triangulation(model),
TestBasis(),
ReferenceDomain())
cell_basis_array, trian, TestBasis(), ReferenceDomain()
)
cell_dof_basis = CellDof(
lazy_map(get_dof_basis,cell_reffe),trian,ReferenceDomain()
)
cell_dof_ids = Fill(Int32(1):Int32(num_indep_components(field_type)),num_cells(trian))

cell_dof_basis_array = lazy_map(get_dof_basis,cell_reffe)
cell_dof_basis = CellDof(cell_dof_basis_array,Triangulation(model),ReferenceDomain())

cell_dof_ids = Fill(Int32(1):Int32(num_indep_components(field_type)),num_cells(model))
A = typeof(cell_basis)
B = typeof(cell_dof_basis)
C = typeof(cell_dof_ids)
new{V,T,A,B,C}(model, cell_basis, cell_dof_basis, cell_dof_ids)
new{V,T,A,B,C}(trian, cell_basis, cell_dof_basis, cell_dof_ids)
end
end

function ConstantFESpace(model::DiscreteModel; kwargs...)
trian = Triangulation(model)
ConstantFESpace(model,trian; kwargs...)
end

function ConstantFESpace(trian::Triangulation; kwargs...)
model = get_active_model(trian)
ConstantFESpace(model,trian; kwargs...)
end

TrialFESpace(f::ConstantFESpace) = f

# Delegated functions
get_triangulation(f::ConstantFESpace) = Triangulation(f.model)
get_triangulation(f::ConstantFESpace) = f.trian

ConstraintStyle(::Type{<:ConstantFESpace}) = UnConstrained()

Expand Down
Loading
Loading