diff --git a/NEWS.md b/NEWS.md index c05813665..bdbfe165c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added +- Implemented (generalised) ModalC0 Polynomial bases and reference FEs. Since PR [#777](https://github.com/gridap/Gridap.jl/pull/777) +- Serendipity reference FEs for any dimension and order. Since PR [#777](https://github.com/gridap/Gridap.jl/pull/777) + ## [0.17.12] - 2022-03-24 ### Fixed diff --git a/Project.toml b/Project.toml index 01cef6535..a61c6f4a2 100644 --- a/Project.toml +++ b/Project.toml @@ -19,6 +19,7 @@ LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56" NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce" +PolynomialBases = "c74db56a-226d-5e98-8bb0-a6049094aeea" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" diff --git a/src/Exports.jl b/src/Exports.jl index 9226e683c..292ce2fdc 100644 --- a/src/Exports.jl +++ b/src/Exports.jl @@ -88,9 +88,11 @@ using Gridap.TensorValues: ⊗; export ⊗ @publish ReferenceFEs Lagrangian @publish ReferenceFEs RaviartThomas @publish ReferenceFEs Nedelec +@publish ReferenceFEs ModalC0 @publish ReferenceFEs lagrangian @publish ReferenceFEs raviart_thomas @publish ReferenceFEs nedelec +@publish ReferenceFEs modalC0 @publish Geometry get_triangulation @publish Geometry num_cells diff --git a/src/Geometry/DiscreteModels.jl b/src/Geometry/DiscreteModels.jl index 9c2854e5c..ad8752d31 100644 --- a/src/Geometry/DiscreteModels.jl +++ b/src/Geometry/DiscreteModels.jl @@ -388,6 +388,12 @@ function ReferenceFE(model::DiscreteModel,args...;kwargs...) cell_to_reffe end +function ReferenceFE(model::DiscreteModel,basis::ModalC0,args...;kwargs...) + ctype_to_polytope = get_polytopes(model) + @assert length(ctype_to_polytope) == 1 "Only one polytope expected" + compute_cell_to_modalC0_reffe(ctype_to_polytope[1],num_cells(model),args...;kwargs...) +end + # IO function to_dict(model::DiscreteModel) diff --git a/src/Polynomials/ModalC0Bases.jl b/src/Polynomials/ModalC0Bases.jl new file mode 100644 index 000000000..1a0df5eb8 --- /dev/null +++ b/src/Polynomials/ModalC0Bases.jl @@ -0,0 +1,532 @@ +struct ModalC0BasisFunction <: Field end + +struct ModalC0Basis{D,T,V} <: AbstractVector{ModalC0BasisFunction} + orders::NTuple{D,Int} + terms::Vector{CartesianIndex{D}} + a::Vector{Point{D,V}} + b::Vector{Point{D,V}} + function ModalC0Basis{D}( + ::Type{T}, + orders::NTuple{D,Int}, + terms::Vector{CartesianIndex{D}}, + a::Vector{Point{D,V}}, + b::Vector{Point{D,V}}) where {D,T,V} + new{D,T,V}(orders,terms,a,b) + end +end + +@inline Base.size(a::ModalC0Basis{D,T,V}) where {D,T,V} = (length(a.terms)*num_components(T),) +@inline Base.getindex(a::ModalC0Basis,i::Integer) = ModalC0BasisFunction() +@inline Base.IndexStyle(::ModalC0Basis) = IndexLinear() + +function ModalC0Basis{D}( + ::Type{T}, + orders::NTuple{D,Int}, + a::Vector{Point{D,V}}, + b::Vector{Point{D,V}}; + filter::Function=_q_filter, + sort!::Function=_sort_by_nfaces!) where {D,T,V} + + terms = _define_terms_mc0(filter, sort!, orders) + ModalC0Basis{D}(T,orders,terms,a,b) +end + +function ModalC0Basis{D}( + ::Type{T}, + orders::NTuple{D,Int}, + sa::Point{D,V}, + sb::Point{D,V}; + filter::Function=_q_filter, + sort!::Function=_sort_by_nfaces!) where {D,T,V} + + terms = _define_terms_mc0(filter, sort!, orders) + a = fill(sa,length(terms)) + b = fill(sb,length(terms)) + ModalC0Basis{D}(T,orders,terms,a,b) +end + +function ModalC0Basis{D}( + ::Type{T}, + orders::NTuple{D,Int}; + filter::Function=_q_filter, + sort!::Function=_sort_by_nfaces!) where {D,T} + + sa = Point{D,eltype(T)}(tfill(zero(eltype(T)),Val{D}())) + sb = Point{D,eltype(T)}(tfill(one(eltype(T)),Val{D}())) + ModalC0Basis{D}(T,orders,sa,sb,filter=filter,sort! = sort!) +end + +function ModalC0Basis{D}( + ::Type{T}, + order::Int, + a::Vector{Point{D,V}}, + b::Vector{Point{D,V}}; + filter::Function=_q_filter, + sort!::Function=_sort_by_nfaces!) where {D,T,V} + + orders = tfill(order,Val{D}()) + ModalC0Basis{D}(T,orders,a,b,filter=filter,sort! = sort!) +end + +function ModalC0Basis{D}( + ::Type{T}, + order::Int; + filter::Function=_q_filter, + sort!::Function=_sort_by_nfaces!) where {D,T} + + orders = tfill(order,Val{D}()) + ModalC0Basis{D}(T,orders,filter=filter,sort! = sort!) +end + +# API + +""" + get_order(b::ModalC0Basis) +""" +function get_order(b::ModalC0Basis) + maximum(b.orders) +end + +""" + get_orders(b::ModalC0Basis) +""" +function get_orders(b::ModalC0Basis) + b.orders +end + +return_type(::ModalC0Basis{D,T,V}) where {D,T,V} = T + +# Field implementation + +function return_cache(f::ModalC0Basis{D,T,V},x::AbstractVector{<:Point}) where {D,T,V} + @assert D == length(eltype(x)) "Incorrect number of point components" + np = length(x) + ndof = length(f.terms)*num_components(T) + n = 1 + _maximum(f.orders) + r = CachedArray(zeros(T,(np,ndof))) + v = CachedArray(zeros(T,(ndof,))) + c = CachedArray(zeros(eltype(T),(D,n))) + (r, v, c) +end + +function evaluate!(cache,f::ModalC0Basis{D,T,V},x::AbstractVector{<:Point}) where {D,T,V} + r, v, c = cache + np = length(x) + ndof = length(f.terms)*num_components(T) + n = 1 + _maximum(f.orders) + setsize!(r,(np,ndof)) + setsize!(v,(ndof,)) + setsize!(c,(D,n)) + for i in 1:np + @inbounds xi = x[i] + _evaluate_nd_mc0!(v,xi,f.a,f.b,f.orders,f.terms,c) + for j in 1:ndof + @inbounds r[i,j] = v[j] + end + end + r.array +end + +function return_cache( + fg::FieldGradientArray{1,ModalC0Basis{D,V,W}}, + x::AbstractVector{<:Point}) where {D,V,W} + + f = fg.fa + @assert D == length(eltype(x)) "Incorrect number of point components" + np = length(x) + ndof = length(f.terms)*num_components(V) + xi = testitem(x) + T = gradient_type(V,xi) + n = 1 + _maximum(f.orders) + r = CachedArray(zeros(T,(np,ndof))) + v = CachedArray(zeros(T,(ndof,))) + c = CachedArray(zeros(eltype(T),(D,n))) + g = CachedArray(zeros(eltype(T),(D,n))) + (r, v, c, g) +end + +function evaluate!( + cache, + fg::FieldGradientArray{1,ModalC0Basis{D,T,V}}, + x::AbstractVector{<:Point}) where {D,T,V} + + f = fg.fa + r, v, c, g = cache + np = length(x) + ndof = length(f.terms) * num_components(T) + n = 1 + _maximum(f.orders) + setsize!(r,(np,ndof)) + setsize!(v,(ndof,)) + setsize!(c,(D,n)) + setsize!(g,(D,n)) + for i in 1:np + @inbounds xi = x[i] + _gradient_nd_mc0!(v,xi,f.a,f.b,f.orders,f.terms,c,g,T) + for j in 1:ndof + @inbounds r[i,j] = v[j] + end + end + r.array +end + +function return_cache( + fg::FieldGradientArray{2,ModalC0Basis{D,V,W}}, + x::AbstractVector{<:Point}) where {D,V,W} + + f = fg.fa + @assert D == length(eltype(x)) "Incorrect number of point components" + np = length(x) + ndof = length(f.terms)*num_components(V) + xi = testitem(x) + T = gradient_type(gradient_type(V,xi),xi) + n = 1 + _maximum(f.orders) + r = CachedArray(zeros(T,(np,ndof))) + v = CachedArray(zeros(T,(ndof,))) + c = CachedArray(zeros(eltype(T),(D,n))) + g = CachedArray(zeros(eltype(T),(D,n))) + h = CachedArray(zeros(eltype(T),(D,n))) + (r, v, c, g, h) +end + +function evaluate!( + cache, + fg::FieldGradientArray{2,ModalC0Basis{D,T,V}}, + x::AbstractVector{<:Point}) where {D,T,V} + + f = fg.fa + r, v, c, g, h = cache + np = length(x) + ndof = length(f.terms) * num_components(T) + n = 1 + _maximum(f.orders) + setsize!(r,(np,ndof)) + setsize!(v,(ndof,)) + setsize!(c,(D,n)) + setsize!(g,(D,n)) + setsize!(h,(D,n)) + for i in 1:np + @inbounds xi = x[i] + _hessian_nd_mc0!(v,xi,f.a,f.b,f.orders,f.terms,c,g,h,T) + for j in 1:ndof + @inbounds r[i,j] = v[j] + end + end + r.array +end + +# Optimizing evaluation at a single point + +function return_cache(f::AbstractVector{ModalC0BasisFunction},x::Point) + xs = [x] + cf = return_cache(f,xs) + v = evaluate!(cf,f,xs) + r = CachedArray(zeros(eltype(v),(size(v,2),))) + r, cf, xs +end + +function evaluate!(cache,f::AbstractVector{ModalC0BasisFunction},x::Point) + r, cf, xs = cache + xs[1] = x + v = evaluate!(cf,f,xs) + ndof = size(v,2) + setsize!(r,(ndof,)) + a = r.array + copyto!(a,v) + a +end + +function return_cache( + f::FieldGradientArray{N,<:AbstractVector{ModalC0BasisFunction}}, x::Point) where {N} + xs = [x] + cf = return_cache(f,xs) + v = evaluate!(cf,f,xs) + r = CachedArray(zeros(eltype(v),(size(v,2),))) + r, cf, xs +end + +function evaluate!( + cache, f::FieldGradientArray{N,<:AbstractVector{ModalC0BasisFunction}}, x::Point) where {N} + r, cf, xs = cache + xs[1] = x + v = evaluate!(cf,f,xs) + ndof = size(v,2) + setsize!(r,(ndof,)) + a = r.array + copyto!(a,v) + a +end + +# Helpers + +_s_filter_mc0(e,o) = ( sum( [ i for i in e if i>1 ] ) <= o ) + +_sort_by_tensor_prod!(terms,orders) = terms + +function _sort_by_nfaces!(terms::Vector{CartesianIndex{D}},orders) where D + + # Generate indices of n-faces and order s.t. + # (1) dimension-increasing (2) lexicographic + bin_rang_nfaces = tfill(0:1,Val{D}()) + bin_ids_nfaces = collect(Iterators.product(bin_rang_nfaces...)) + sum_bin_ids_nfaces = [sum(bin_ids_nfaces[i]) for i in eachindex(bin_ids_nfaces)] + bin_ids_nfaces = permute!(bin_ids_nfaces,sortperm(sum_bin_ids_nfaces)) + + # Generate LIs of basis funs s.t. order by n-faces + lids_b = LinearIndices(Tuple([orders[i]+1 for i=1:D])) + + eet = eltype(eltype(bin_ids_nfaces)) + f(x) = Tuple( x[i] == one(eet) ? (0:0) : (1:2) for i in 1:length(x) ) + g(x) = Tuple( x[i] == one(eet) ? (3:orders[i]+1) : (0:0) for i in 1:length(x) ) + rang_nfaces = map(f,bin_ids_nfaces) + rang_own_dofs = map(g,bin_ids_nfaces) + + P = Int64[] + for i = 1:length(bin_ids_nfaces) + cis_nfaces = CartesianIndices(rang_nfaces[i]) + cis_own_dofs = CartesianIndices(rang_own_dofs[i]) + for ci in cis_nfaces + ci = ci .+ cis_own_dofs + P = vcat(P,reshape(lids_b[ci],length(ci))) + end + end + + permute!(terms,P) +end + +function _compute_filter_mask(terms,filter,orders) + g = (0 .* orders) .+ 1 + to = CartesianIndex(g) + maxorder = _maximum(orders) + term_to_is_fterm = lazy_map(t->filter(Int[Tuple(t-to)...],maxorder),terms) + findall(term_to_is_fterm) +end + +function _define_terms_mc0(filter,sort!,orders) + terms = _define_terms(_q_filter,orders) + sort!(terms,orders) + mask = _compute_filter_mask(terms,filter,orders) + collect(lazy_map(Reindex(terms),mask)) +end + +function _evaluate_1d_mc0!(v::AbstractMatrix{T},x,a,b,order,d) where T + @assert order > 0 + n = order + 1 + z = one(T) + @inbounds v[d,1] = z - x[d] + @inbounds v[d,2] = x[d] + if n > 2 + ξ = ( 2*x[d] - ( a[d] + b[d] ) ) / ( b[d] - a[d] ) + for i in 3:n + @inbounds v[d,i] = -sqrt(2*i-3)*v[d,1]*v[d,2]*jacobi(ξ,i-3,1,1)/(i-2) + end + end +end + +function _gradient_1d_mc0!(v::AbstractMatrix{T},x,a,b,order,d) where T + @assert order > 0 + n = order + 1 + z = one(T) + @inbounds v[d,1] = -z + @inbounds v[d,2] = z + if n > 2 + ξ = ( 2*x[d] - ( a[d] + b[d] ) ) / ( b[d] - a[d] ) + v1 = z - x[d] + v2 = x[d] + for i in 3:n + j, dj = jacobi_and_derivative(ξ,i-3,1,1) + @inbounds v[d,i] = -sqrt(2*i-3)*(v[d,1]*v2*j+v1*v[d,2]*j+v1*v2*(2/(b[d]-a[d]))*dj)/(i-2) + end + end +end + +function _hessian_1d_mc0!(v::AbstractMatrix{T},x,a,b,order,d) where T + @assert order > 0 + n = order + 1 + y = zero(T) + z = one(T) + @inbounds v[d,1] = y + @inbounds v[d,2] = y + if n > 2 + ξ = ( 2*x[d] - ( a[d] + b[d] ) ) / ( b[d] - a[d] ) + v1 = z - x[d] + v2 = x[d] + dv1 = -z + dv2 = z + for i in 3:n + j, dj = jacobi_and_derivative(ξ,i-3,1,1) + _, d2j = jacobi_and_derivative(ξ,i-4,2,2) + @inbounds v[d,i] = -sqrt(2*i-3)*(2*dv1*dv2*j+2*(dv1*v2+v1*dv2)*(2/(b[d]-a[d]))*dj+v1*v2*d2j*2*i*((b[d]-a[d])^2))/(i-2) + end + end +end + +function _evaluate_nd_mc0!( + v::AbstractVector{V}, + x, + a::Vector{Point{D,T}}, + b::Vector{Point{D,T}}, + orders, + terms::AbstractVector{CartesianIndex{D}}, + c::AbstractMatrix{T}) where {V,T,D} + + dim = D + o = one(T) + k = 1 + l = length(terms) + + for (i,ci) in enumerate(terms) + + for d in 1:dim + _evaluate_1d_mc0!(c,x,a[i],b[i],orders[d],d) + end + + s = o + for d in 1:dim + @inbounds s *= c[d,ci[d]] + end + + k = _set_value_mc0!(v,s,k,l) + + end + +end + +@inline function _set_value_mc0!(v::AbstractVector{V},s::T,k,l) where {V,T} + m = zero(Mutable(V)) + z = zero(T) + js = eachindex(m) + for j in js + for i in js + @inbounds m[i] = z + end + @inbounds m[j] = s + i = k+l*(j-1) + @inbounds v[i] = m + end + k+1 +end + +@inline function _set_value_mc0!(v::AbstractVector{<:Real},s,k,l) + @inbounds v[k] = s + k+1 +end + +function _gradient_nd_mc0!( + v::AbstractVector{G}, + x, + a::Vector{Point{D,T}}, + b::Vector{Point{D,T}}, + orders, + terms::AbstractVector{CartesianIndex{D}}, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + ::Type{V}) where {G,T,D,V} + + dim = D + z = zero(Mutable(VectorValue{D,T})) + o = one(T) + k = 1 + l = length(terms) + + for (i,ci) in enumerate(terms) + + for d in 1:dim + _evaluate_1d_mc0!(c,x,a[i],b[i],orders[d],d) + _gradient_1d_mc0!(g,x,a[i],b[i],orders[d],d) + end + + s = z + for i in eachindex(s) + @inbounds s[i] = o + end + for q in 1:dim + for d in 1:dim + if d != q + @inbounds s[q] *= c[d,ci[d]] + else + @inbounds s[q] *= g[d,ci[d]] + end + end + end + + k = _set_gradient_mc0!(v,s,k,l,V) + + end + +end + +@inline function _set_gradient_mc0!( + v::AbstractVector{G},s,k,l,::Type{<:Real}) where G + + @inbounds v[k] = s + k+1 +end + +@inline function _set_gradient_mc0!( + v::AbstractVector{G},s,k,l,::Type{V}) where {V,G} + + T = eltype(s) + m = zero(Mutable(G)) + w = zero(V) + z = zero(T) + for (ij,j) in enumerate(CartesianIndices(w)) + for i in CartesianIndices(m) + @inbounds m[i] = z + end + for i in CartesianIndices(s) + @inbounds m[i,j] = s[i] + end + i = k+l*(ij-1) + @inbounds v[i] = m + end + k+1 +end + +function _hessian_nd_mc0!( + v::AbstractVector{G}, + x, + a::Vector{Point{D,T}}, + b::Vector{Point{D,T}}, + orders, + terms::AbstractVector{CartesianIndex{D}}, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + h::AbstractMatrix{T}, + ::Type{V}) where {G,T,D,V} + + dim = D + z = zero(Mutable(TensorValue{D,D,T})) + o = one(T) + k = 1 + l = length(terms) + + for (i,ci) in enumerate(terms) + + for d in 1:dim + _evaluate_1d_mc0!(c,x,a[i],b[i],orders[d],d) + _gradient_1d_mc0!(g,x,a[i],b[i],orders[d],d) + _hessian_1d_mc0!(h,x,a[i],b[i],orders[d],d) + end + + s = z + for i in eachindex(s) + @inbounds s[i] = o + end + for r in 1:dim + for q in 1:dim + for d in 1:dim + if d != q && d != r + @inbounds s[r,q] *= c[d,ci[d]] + elseif d == q && d ==r + @inbounds s[r,q] *= h[d,ci[d]] + else + @inbounds s[r,q] *= g[d,ci[d]] + end + end + end + end + + k = _set_gradient_mc0!(v,s,k,l,V) + + end + +end \ No newline at end of file diff --git a/src/Polynomials/Polynomials.jl b/src/Polynomials/Polynomials.jl index 20a51068d..9b955a1eb 100644 --- a/src/Polynomials/Polynomials.jl +++ b/src/Polynomials/Polynomials.jl @@ -14,6 +14,8 @@ using Gridap.Arrays using Gridap.TensorValues using Gridap.Fields +using PolynomialBases: jacobi, jacobi_and_derivative + import Gridap.Fields: evaluate! import Gridap.Fields: return_cache import Gridap.Arrays: return_type @@ -22,6 +24,7 @@ export MonomialBasis export QGradMonomialBasis export QCurlGradMonomialBasis export PCurlGradMonomialBasis +export ModalC0Basis export get_exponents export get_order @@ -36,4 +39,6 @@ include("QCurlGradMonomialBases.jl") include("PCurlGradMonomialBases.jl") +include("ModalC0Bases.jl") + end # module diff --git a/src/ReferenceFEs/LinearCombinationDofVectors.jl b/src/ReferenceFEs/LinearCombinationDofVectors.jl new file mode 100644 index 000000000..507a34724 --- /dev/null +++ b/src/ReferenceFEs/LinearCombinationDofVectors.jl @@ -0,0 +1,46 @@ +""" + struct LinearCombinationDofVector{T} <: AbstractVector{Dof} + change_of_basis::Matrix{T} + dof_basis::AbstractVector{<:Dof} + end + +Type that implements a dof basis (a) as the linear combination of a dof basis +(b). The dofs are first evaluated at dof basis (b) (field `dof_basis`) and the +dof values are next mapped to dof basis (a) applying a change of basis (field +`change_of_basis`). + +Fields: + +- `change_of_basis::Matrix{T}` the matrix of the change from dof basis (b) to (a) +- `dof_basis::AbstractVector{<:Dof}` A type representing dof basis (b) +""" +struct LinearCombinationDofVector{T} <: AbstractVector{Dof} + change_of_basis::Matrix{T} + dof_basis::AbstractVector{<:Dof} +end + +@inline Base.size(a::LinearCombinationDofVector) = size(a.dof_basis) +@inline Base.axes(a::LinearCombinationDofVector) = axes(a.dof_basis) +@inline Base.getindex(a::LinearCombinationDofVector,i::Integer) = getindex(a.dof_basis,i) +@inline Base.IndexStyle(::LinearCombinationDofVector) = IndexLinear() + +function linear_combination(a::AbstractMatrix{<:Number}, + b::AbstractVector{<:Dof}) + LinearCombinationDofVector(a,b) +end + +function linear_combination(a::LinearCombinationDofVector{T}, + b::AbstractVector{<:Dof}) where T + linear_combination(a.change_of_basis,b) +end + +function return_cache(b::LinearCombinationDofVector,field) + c, cf = return_cache(b.dof_basis,field) + c, cf, return_cache(*,b.change_of_basis,c) +end + +@inline function evaluate!(cache,b::LinearCombinationDofVector,field) + c, cf, cc = cache + vals = evaluate!(cache,b.dof_basis,field) + evaluate!(cc,*,b.change_of_basis,vals) +end \ No newline at end of file diff --git a/src/ReferenceFEs/ModalC0RefFEs.jl b/src/ReferenceFEs/ModalC0RefFEs.jl new file mode 100644 index 000000000..c02d03c02 --- /dev/null +++ b/src/ReferenceFEs/ModalC0RefFEs.jl @@ -0,0 +1,196 @@ +struct ModalC0 <: ReferenceFEName end + +const modalC0 = ModalC0() + +""" + ModalC0RefFE(::Type{T},p::Polytope{D},orders) where {T,D} + +Returns an instance of `GenericRefFE{ModalC0}` representing a ReferenceFE with +Modal C0-continuous shape functions (multivariate scalar-valued, vector-valued, +or tensor-valued, iso- or aniso-tropic). + +For more details about the shape functions, see Section 1.1.5 in + +Ern, A., & Guermond, J. L. (2013). Theory and practice of finite elements +(Vol. 159). Springer Science & Business Media. + +and references therein. + +The constructor is only implemented for for n-cubes and the minimum order in +any dimension must be greater than one. The DoFs are numbered by n-faces in the +same way as with CLagrangianRefFEs. +""" +function ModalC0RefFE( + ::Type{T}, + p::Polytope{D}, + orders, + a::Vector{Point{D,V}}, + b::Vector{Point{D,V}}; + space::Symbol=_default_space(p) ) where {T,D,V} + + @notimplementedif ! is_n_cube(p) + @notimplementedif minimum(orders) < one(eltype(orders)) + + shapefuns = ModalC0Basis{D}(T,orders,a,b) + + ndofs, predofs, lag_reffe, face_dofs = compute_reffe_data(T,p,orders,space=space) + + GenericRefFE{ModalC0}( + ndofs, + p, + predofs, + GradConformity(), + lag_reffe, + face_dofs, + shapefuns) +end + +function ModalC0RefFE( + ::Type{T}, + p::Polytope{D}, + orders; + space::Symbol=_default_space(p) ) where {T,D,V} + + @notimplementedif ! is_n_cube(p) + @notimplementedif minimum(orders) < one(eltype(orders)) + + shapefuns = ModalC0Basis{D}(T,orders) + + ndofs, predofs, lag_reffe, face_dofs = compute_reffe_data(T,p,orders,space=space) + + GenericRefFE{ModalC0}( + ndofs, + p, + predofs, + GradConformity(), + lag_reffe, + face_dofs, + shapefuns) +end + +function get_orders(reffe::GenericRefFE{ModalC0,D}) where{D} + get_orders(reffe.prebasis) +end + +function compute_reffe_data(::Type{T}, + p::Polytope{D}, + order::Int; + space::Symbol=_default_space(p)) where {T,D} + orders = tfill(order,Val{D}()) + compute_reffe_data(T,p,orders,space=space) +end + +function compute_reffe_data(::Type{T}, + p::Polytope{D}, + orders::NTuple{D,Int}; + space::Symbol=_default_space(p)) where {T,D} + lag_reffe = LagrangianRefFE(T,p,orders,space=space) + reffe = lag_reffe.reffe + reffe.ndofs, reffe.dofs, lag_reffe, reffe.face_dofs +end + +function ReferenceFE( + polytope::Polytope{D}, + ::ModalC0, + ::Type{T}, + orders::Union{Integer,NTuple{D,Int}}; + kwargs...) where {T,D} + ModalC0RefFE(T,polytope,orders;kwargs...) +end + +function Conformity(::GenericRefFE{ModalC0},sym::Symbol) + h1 = (:H1,:C0,:Hgrad) + if sym == :L2 + L2Conformity() + elseif sym in h1 + H1Conformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a ModalC0RefFE with H1 conformity. + Possible values of conformity for this reference fe are $((:L2, h1...)). + """ + end +end + +function get_face_own_dofs( + reffe::GenericRefFE{ModalC0},conf::GradConformity) + lagrangian_reffe = reffe.metadata + get_face_own_dofs(lagrangian_reffe,conf) +end + +function get_face_own_dofs_permutations( + reffe::GenericRefFE{ModalC0},conf::GradConformity) + lagrangian_reffe = reffe.metadata + get_face_own_dofs_permutations(lagrangian_reffe,conf) +end + +function compute_shapefun_bboxes!( + a::Vector{Point{D,V}}, + b::Vector{Point{D,V}}, + bboxes::Vector{Point{D,V}}, + face_own_dofs) where {D,V} + for i in 1:length(face_own_dofs) + a[face_own_dofs[i]] .= bboxes[2*i-1] + b[face_own_dofs[i]] .= bboxes[2*i] + end +end + +function compute_cell_to_modalC0_reffe( + p::Polytope{D}, + ncells::Int, + ::Type{T}, + orders::Union{Integer,NTuple{D,Int}}, + bboxes; + space::Symbol=_default_space(p)) where {T,D} # type-stability? + + @notimplementedif ! is_n_cube(p) + @notimplementedif minimum(orders) < one(eltype(orders)) + @assert ncells == length(bboxes) + + ndofs, predofs, lag_reffe, face_dofs = compute_reffe_data(T,p,orders,space=space) + face_own_dofs = get_face_own_dofs(lag_reffe,GradConformity()) + + filter = space == :Q ? _q_filter : _s_filter_mc0 + + sh(bbs) = begin + a = fill(Point{D,eltype(T)}(tfill(zero(eltype(T)),Val{D}())),ndofs) + b = fill(Point{D,eltype(T)}(tfill(zero(eltype(T)),Val{D}())),ndofs) + compute_shapefun_bboxes!(a,b,bbs,face_own_dofs) + ModalC0Basis{D}(T,orders,a,b,filter=filter) + end + + reffe(sh) = GenericRefFE{ModalC0}(ndofs, + p, + predofs, + GradConformity(), + lag_reffe, + face_dofs, + sh) + + reffes = [ reffe(sh(bbs)) for bbs in bboxes ] + CompressedArray(reffes,1:ncells) +end + +function compute_cell_to_modalC0_reffe( + p::Polytope{D}, + ncells::Int, + ::Type{T}, + orders::Union{Integer,NTuple{D,Int}}; + space::Symbol=_default_space(p)) where {T,D} # type-stability? + + @notimplementedif ! is_n_cube(p) + @notimplementedif minimum(orders) < one(eltype(orders)) + + filter = space == :Q ? _q_filter : _s_filter_mc0 + + ndofs, predofs, lag_reffe, face_dofs = compute_reffe_data(T,p,orders,space=space) + reffe = GenericRefFE{ModalC0}(ndofs, + p, + predofs, + GradConformity(), + lag_reffe, + face_dofs, + ModalC0Basis{D}(T,orders,filter=filter)) + + Fill(reffe,ncells) +end \ No newline at end of file diff --git a/src/ReferenceFEs/ReferenceFEInterfaces.jl b/src/ReferenceFEs/ReferenceFEInterfaces.jl index db48d8744..1542801b0 100644 --- a/src/ReferenceFEs/ReferenceFEInterfaces.jl +++ b/src/ReferenceFEs/ReferenceFEInterfaces.jl @@ -382,6 +382,22 @@ function compute_shapefuns(dofs,prebasis) linear_combination(change,prebasis) end +""" + compute_dofs(predofs,shapefuns) + +Helper function used to compute the dof basis +associated with the dof basis `predofs` and the basis `shapefuns`. + +It is equivalent to + + change = inv(evaluate(predofs,shapefuns)) + linear_combination(change,predofs) # i.e. transpose(change)*predofs +""" +function compute_dofs(predofs,shapefuns) + change = inv(evaluate(predofs,shapefuns)) + linear_combination(change,predofs) +end + # Concrete implementation """ @@ -440,6 +456,40 @@ struct GenericRefFE{T,D} <: ReferenceFE{D} face_dofs, shapefuns) end + @doc """ + GenericRefFE{T}( + ndofs::Int, + polytope::Polytope{D}, + prebasis::AbstractVector{<:Field}, + predofs::AbstractVector{<:Dof}, + conformity::Conformity, + metadata, + face_dofs::Vector{Vector{Int}}, + shapefuns::AbstractVector{<:Field}, + dofs::AbstractVector{<:Dof}=compute_dofs(predofs,shapefuns)) where {T,D} + + Constructs a `GenericRefFE` object with the provided data. + """ + function GenericRefFE{T}( + ndofs::Int, + polytope::Polytope{D}, + predofs::AbstractVector{<:Dof}, + conformity::Conformity, + metadata, + face_dofs::Vector{Vector{Int}}, + shapefuns::AbstractVector{<:Field}, + dofs::AbstractVector{<:Dof}=compute_dofs(predofs,shapefuns)) where {T,D} + + new{T,D}( + ndofs, + polytope, + shapefuns, + dofs, + conformity, + metadata, + face_dofs, + linear_combination(Eye{Int}(ndofs),shapefuns)) # Trick to be able to eval dofs af shapefuns in physical space + end end num_dofs(reffe::GenericRefFE) = reffe.ndofs diff --git a/src/ReferenceFEs/ReferenceFEs.jl b/src/ReferenceFEs/ReferenceFEs.jl index 9eab471b5..502b395ad 100644 --- a/src/ReferenceFEs/ReferenceFEs.jl +++ b/src/ReferenceFEs/ReferenceFEs.jl @@ -17,6 +17,10 @@ using Gridap.TensorValues using Gridap.Fields using Gridap.Polynomials +using Gridap.Polynomials: _q_filter, _s_filter_mc0 +using Gridap.Polynomials: _compute_filter_mask +using Gridap.Polynomials: _define_terms, _sort_by_nfaces! + using QuadGK: gauss using FastGaussQuadrature: gaussjacobi using FastGaussQuadrature: gausslegendre @@ -26,10 +30,13 @@ import Gridap.Arrays: evaluate! import Gridap.Arrays: return_type import Gridap.Fields: evaluate import Gridap.Fields: lazy_map +import Gridap.Fields: linear_combination import Gridap.Polynomials: MonomialBasis import Gridap.Polynomials: get_order import Gridap.Polynomials: get_orders +import Gridap.Polynomials: _compute_filter_mask +import Gridap.Polynomials: _define_terms, _sort_by_nfaces! import Gridap.Io: to_dict import Gridap.Io: from_dict @@ -145,6 +152,9 @@ export MomentBasedDofBasis export get_face_own_nodes export get_face_nodes +export linear_combination +export compute_cell_to_modalC0_reffe + export VERTEX1 export SEG2 export TRI3 @@ -167,16 +177,19 @@ export SerendipityRefFE export RaviartThomasRefFE export NedelecRefFE export BezierRefFE +export ModalC0RefFE export Lagrangian export RaviartThomas export Nedelec export Bezier +export ModalC0 export lagrangian export raviart_thomas export nedelec export bezier +export modalC0 export Quadrature export QuadratureName @@ -228,4 +241,8 @@ include("MockDofs.jl") include("BezierRefFEs.jl") +include("ModalC0RefFEs.jl") + +include("LinearCombinationDofVectors.jl") + end # module diff --git a/src/ReferenceFEs/SerendipityRefFEs.jl b/src/ReferenceFEs/SerendipityRefFEs.jl index 258b9fe64..935a72c0e 100644 --- a/src/ReferenceFEs/SerendipityRefFEs.jl +++ b/src/ReferenceFEs/SerendipityRefFEs.jl @@ -103,24 +103,19 @@ function compute_own_nodes(p::SerendipityPolytope{1},orders) compute_own_nodes(p.hex,orders) end -function compute_own_nodes(p::SerendipityPolytope{2},orders) - order, = orders - if order == 4 - o = (2,2) - elseif order in (0,1,2,3) - o=(1,1) - else - @unreachable "Serendipity elements only up to order 4" - end - compute_own_nodes(p.hex,o) -end +_own_s_filter(e,o) = ( sum( [ i for i in e ] ) <= o && all( [ i > 1 for i in e ] ) ) -function compute_own_nodes(p::SerendipityPolytope{3},orders) - Point{3,Float64}[] +function _compute_own_s_nodes(orders) + _terms = _define_terms(_q_filter,orders) + _sort_by_nfaces!(_terms,orders) + mask = _compute_filter_mask(_terms,_own_s_filter,orders) + _terms = lazy_map(Reindex(_terms),mask) + terms = map(t->CartesianIndex(Tuple(t)),_terms) + _terms_to_coords(terms,orders) end function compute_own_nodes(p::SerendipityPolytope,orders) - @unreachable "Serendipity elements only up to 3d" + _compute_own_s_nodes(orders) end function compute_face_orders( diff --git a/test/PolynomialsTests/ModalC0BasesTests.jl b/test/PolynomialsTests/ModalC0BasesTests.jl new file mode 100644 index 000000000..577ba9737 --- /dev/null +++ b/test/PolynomialsTests/ModalC0BasesTests.jl @@ -0,0 +1,65 @@ +module ModalC0BasesTests + +using Test +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Polynomials +# using BenchmarkTools + +import Gridap.Fields: Broadcasting + +# Real-valued Q space with isotropic order + +x1 = Point(0.0) +x2 = Point(0.5) +x3 = Point(1.0) + +V = Float64 +G = gradient_type(V,x1) +H = gradient_type(G,x1) +order = 12 + +order = 3 +a = fill(Point(-0.5),order+1) +b = fill(Point(2.5),order+1) +b1 = ModalC0Basis{1}(V,order,a,b) +∇b1 = Broadcasting(∇)(b1) +∇∇b1 = Broadcasting(∇)(∇b1) + +@test evaluate(b1,[x1,x2,x3,]) ≈ [1.0 0.0 -0.0 0.0; + 0.5 0.5 -0.4330127018922193 0.18633899812498247; + 0.0 1.0 -0.0 -0.0] +@test evaluate(∇b1,[x1,x2,x3,]) ≈ G[(-1.0,) (1.0,) (-1.7320508075688772,) (1.4907119849998598,); + (-1.0,) (1.0,) (-0.0,) (-0.37267799624996495,); + (-1.0,) (1.0,) (1.7320508075688772,) (-0.0,)] +@test evaluate(∇∇b1,[x1,x2,x3,]) ≈ H[(0.0,) (0.0,) (3.4641016151377544,) (-5.962847939999439,); + (0.0,) (0.0,) (3.4641016151377544,) (-1.4907119849998598,); + (0.0,) (0.0,) (3.4641016151377544,) (2.9814239699997196,)] + +x1 = Point(0.0,0.0) +x2 = Point(0.5,0.5) +x3 = Point(1.0,1.0) +a = [ Point(0.0,0.0),Point(0.0,0.0),Point(0.0,0.0),Point(0.0,0.0), + Point(-0.5,2.5),Point(-0.5,2.5),Point(0.0,1.5),Point(0.0,1.5), + Point(-1.0,-1.0),Point(-1.0,-1.0),Point(-1.0,-1.0),Point(-1.0,-1.0), + Point(0.0,0.0),Point(0.0,0.0),Point(0.0,0.0),Point(0.0,0.0) ] +b = [ Point(1.0,1.0),Point(1.0,1.0),Point(1.0,1.0),Point(1.0,1.0), + Point(2.5,2.5),Point(2.5,2.5),Point(1.5,1.5),Point(1.5,1.5), + Point(-1.0,1.0),Point(-1.0,1.0),Point(-1.0,1.25),Point(-1.0,1.25), + Point(1.0,1.0),Point(1.0,1.0),Point(1.0,1.0),Point(1.0,1.0) ] +b2 = ModalC0Basis{2}(V,order,a,b) +∇b2 = Broadcasting(∇)(b2) +∇∇b2 = Broadcasting(∇)(∇b2) + +G = gradient_type(V,x1) +H = gradient_type(G,x1) + +@test evaluate(b2,[x1,x2,x3,]) ≈ [ 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0; + 0.25 0.25 0.25 0.25 -0.21650635094610965 0.09316949906249124 -0.21650635094610965 0.09316949906249124 -0.21650635094610965 -0.13975424859373686 -0.21650635094610965 -0.09316949906249122 0.18749999999999997 0.0 0.0 0.0; + 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ] +@test evaluate(∇b2,[x1,x2,x3,])[:,10] ≈ G[ (0.0, 0.0,); (0.2795084971874737, -0.2795084971874737,); (0.0, 0.0,) ] +@test evaluate(∇∇b2,[x1,x2,x3,])[:,10] ≈ H[ (0.0, 0.0, 0.0, -4.47213595499958); + (0.0, 0.5590169943749475, 0.5590169943749475, 1.118033988749895); + (0.0, -2.23606797749979, -2.23606797749979, 0.0) ] + +end # module \ No newline at end of file diff --git a/test/PolynomialsTests/runtests.jl b/test/PolynomialsTests/runtests.jl index 0f9feb15d..eb14c265f 100644 --- a/test/PolynomialsTests/runtests.jl +++ b/test/PolynomialsTests/runtests.jl @@ -10,6 +10,8 @@ using Test @testset "PCurlGradMonomialBases" begin include("PCurlGradMonomialBasesTests.jl") end +@testset "ModalC0Bases" begin include("ModalC0BasesTests.jl") end + #@testset "ChangeBasis" begin include("ChangeBasisTests.jl") end end # module diff --git a/test/ReferenceFEsTests/ModalC0RefFEsTests.jl b/test/ReferenceFEsTests/ModalC0RefFEsTests.jl new file mode 100644 index 000000000..9f2a6983e --- /dev/null +++ b/test/ReferenceFEsTests/ModalC0RefFEsTests.jl @@ -0,0 +1,153 @@ +module ModalC0RefFEsTests + +using Test +using Gridap +using Gridap.ReferenceFEs +using Gridap.FESpaces +using Gridap.Fields +using Gridap.CellData + +# using BenchmarkTools + +order = 1 +p = QUAD +T = VectorValue{2,Float64} + +m = ReferenceFE(p,modalC0,T,order) +l = ReferenceFE(p,lagrangian,T,order) + +test_reference_fe(m) + +@test num_dofs(m) == num_dofs(l) +@test Conformity(m) === Conformity(l) +@test get_face_own_dofs(m,Conformity(m)) == get_face_own_dofs(l,Conformity(l)) +@test get_face_own_dofs_permutations(m,Conformity(m)) == get_face_own_dofs_permutations(l,Conformity(l)) + +# domain = (0,1,0,1) +# partition = (2,2) +# model = CartesianDiscreteModel(domain,partition) + +function _test_function_interpolation(order,u,V) + test_single_field_fe_space(V) + uh = interpolate(u,V) + Ω = Triangulation(model) + degree = 2*order + dΩ = Measure(Ω,degree) + l2(u) = sqrt(sum( ∫( u⊙u )*dΩ )) + e = u - uh + el2 = l2(e) + @test el2 < 1.0e-9 + # writevtk(Ω,"results",nsubcells=20,cellfields=["uh"=>uh]) + # free_vals = zeros(num_free_dofs(V)); free_vals[7] = 1 + # uh = FEFunction(V,free_vals) + # writevtk(Ω,"shape_7",nsubcells=20,cellfields=["s7"=>uh]) + # free_vals = zeros(num_free_dofs(V)); free_vals[9] = 1 + # uh = FEFunction(V,free_vals) + # writevtk(Ω,"shape_9",nsubcells=20,cellfields=["s9"=>uh]) + # free_vals = zeros(num_free_dofs(V)); free_vals[11] = 1 + # uh = FEFunction(V,free_vals) + # writevtk(Ω,"shape_11",nsubcells=20,cellfields=["s11"=>uh]) + # free_vals = zeros(num_free_dofs(V)); free_vals[13] = 1 + # uh = FEFunction(V,free_vals) + # writevtk(Ω,"shape_13",nsubcells=20,cellfields=["s13"=>uh]) +end + +function test_function_interpolation(::Type{T},order,C,u) where T + reffe = ReferenceFE(modalC0,T,order) + V = FESpace(model,reffe,conformity=C) + _test_function_interpolation(order,u,V) +end + +function test_function_interpolation(::Type{T},order,C,u,bboxes,space) where T + reffe = ReferenceFE(modalC0,T,order,bboxes,space=space) + V = FESpace(model,reffe,conformity=C) + _test_function_interpolation(order,u,V) +end + +domain = (0,1) +partition = (1,) +model = CartesianDiscreteModel(domain,partition) +trian = Triangulation(model) + +T = Float64; order = 3; C = :H1; u(x) = x[1]^3 +bboxes = [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-3.0),Point(1.0)] +bboxes = CellPoint(fill(bboxes,1),trian,PhysicalDomain()) +test_function_interpolation(T,order,C,u,bboxes.cell_ref_point,:Q) + +domain = (0,1) +partition = (4,) +model = CartesianDiscreteModel(domain,partition) + +T = Float64; order = 3; C = :H1; u(x) = x[1]^3 +bboxes = [ [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-1.0),Point(3.0)], + [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-1.5),Point(3.2)], + [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-0.2),Point(1.0)], + [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-1.2),Point(2.0)] ] +test_function_interpolation(T,order,C,u,bboxes,:Q) + +domain = (0,4) +partition = (4,) +model = CartesianDiscreteModel(domain,partition) + +T = Float64; order = 3; C = :H1; u(x) = x[1]^3 +bboxes = [ [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-1.0),Point(3.0)], + [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-1.5),Point(3.2)], + [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-0.2),Point(1.0)], + [Point(0.0),Point(1.0),Point(0.0),Point(1.0),Point(-1.2),Point(2.0)] ] +test_function_interpolation(T,order,C,u,bboxes,:Q) + +domain = (0,1,0,1) +partition = (2,2,) +model = CartesianDiscreteModel(domain,partition) +trian = Triangulation(model) + +T = Float64; order = 3; C = :H1; u(x) = (x[1]+x[2])^3 +bboxes = reshape( [Point(0.0,0.0),Point(1.0,1.0),Point(0.0,0.0), + Point(1.0,1.0),Point(0.0,0.0),Point(1.0,1.0), + Point(0.0,0.0),Point(1.0,1.0),Point(-0.5,2.5), + Point(2.5,2.5),Point(-0.5,2.5),Point(2.5,2.5), + Point(-1.0,-1.0),Point(-1.0,1.25),Point(-1.0,-1.0), + Point(-1.0,1.25),Point(0.0,0.0),Point(1.0,1.0)], 18 ) +bboxes = CellPoint(fill(bboxes,4),trian,PhysicalDomain()) +test_function_interpolation(T,order,C,u,bboxes.cell_ref_point,:Q) + +T = Float64; order = 5; C = :H1; u(x) = (x[1]+x[2])^5 +bboxes = reshape( [Point(0.0,0.0),Point(1.0,1.0),Point(0.0,0.0), + Point(1.0,1.0),Point(0.0,0.0),Point(1.0,1.0), + Point(0.0,0.0),Point(1.0,1.0),Point(-0.5,2.5), + Point(2.5,2.5),Point(-0.5,2.5),Point(2.5,2.5), + Point(-1.0,-1.0),Point(-1.0,1.25),Point(-1.0,-1.0), + Point(-1.0,1.25),Point(0.0,0.0),Point(1.0,1.0)], 18 ) +bboxes = CellPoint(fill(bboxes,4),trian,PhysicalDomain()) +test_function_interpolation(T,order,C,u,bboxes.cell_ref_point,:S) + +order = 1; T = Float64; C = :L2; u(x) = x[1]+x[2] +test_function_interpolation(T,order,C,u) + +order = 1; T = VectorValue{2,Float64}; C = :H1 +u(x) = VectorValue(x[1]+x[2],x[2]) +test_function_interpolation(T,order,C,u) + +domain = (0,1,0,1,0,1) +partition = (2,2,2) +model = CartesianDiscreteModel(domain,partition) + +order = 1; T = Float64; C = :H1; u(x) = x[1]+x[2]+x[3] +test_function_interpolation(T,order,C,u) + +# Inspect operator matrix to check if L2-scalar product of +# gradients of bubble functions satisfy Kronecker's delta +# domain = (0,1) +# partition = (1) +# model = CartesianDiscreteModel(domain,partition) +# order = 6; T = Float64; C = :H1; +# reffe = ReferenceFE(modalC0,T,order) +# V = FESpace(model,reffe,conformity=C) +# Ω = Triangulation(model) +# degree = 2*order +# dΩ = LebesgueMeasure(Ω,degree) +# a(u,v) = ∫( ∇(v)⊙∇(u) )*dΩ +# b(v) = 0.0 +# op = AffineFEOperator(bboxes,V,V) + +end # module diff --git a/test/ReferenceFEsTests/runtests.jl b/test/ReferenceFEsTests/runtests.jl index 5f71aff71..45faf6f44 100644 --- a/test/ReferenceFEsTests/runtests.jl +++ b/test/ReferenceFEsTests/runtests.jl @@ -38,4 +38,6 @@ using Test @testset "BezierRefFEs" begin include("BezierRefFEsTests.jl") end +@testset "ModalC0RefFEs" begin include("ModalC0RefFEsTests.jl") end + end # module