From 901f6a1a0ac326b20ebfc8b69171226923b515ea Mon Sep 17 00:00:00 2001 From: Eric Neiva Date: Fri, 31 Mar 2023 18:36:43 +0200 Subject: [PATCH 1/3] Adds Jacobi polynomial bases --- src/Polynomials/JacobiPolynomialBases.jl | 359 +++++++++++++++++++++ src/Polynomials/Polynomials.jl | 3 + test/PolynomialsTests/ModalC0BasesTests.jl | 1 - test/PolynomialsTests/runtests.jl | 2 + 4 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 src/Polynomials/JacobiPolynomialBases.jl diff --git a/src/Polynomials/JacobiPolynomialBases.jl b/src/Polynomials/JacobiPolynomialBases.jl new file mode 100644 index 000000000..20413cca6 --- /dev/null +++ b/src/Polynomials/JacobiPolynomialBases.jl @@ -0,0 +1,359 @@ +struct JacobiPolynomial <: Field end + +struct JacobiPolynomialBasis{D,T} <: AbstractVector{JacobiPolynomial} + orders::NTuple{D,Int} + terms::Vector{CartesianIndex{D}} + function JacobiPolynomialBasis{D}( + ::Type{T}, orders::NTuple{D,Int}, terms::Vector{CartesianIndex{D}}) where {D,T} + new{D,T}(orders,terms) + end +end + +@inline Base.size(a::JacobiPolynomialBasis{D,T}) where {D,T} = (length(a.terms)*num_components(T),) +@inline Base.getindex(a::JacobiPolynomialBasis,i::Integer) = JacobiPolynomial() +@inline Base.IndexStyle(::JacobiPolynomialBasis) = IndexLinear() + +function JacobiPolynomialBasis{D}( + ::Type{T}, orders::NTuple{D,Int}, filter::Function=_q_filter) where {D,T} + + terms = _define_terms(filter, orders) + JacobiPolynomialBasis{D}(T,orders,terms) +end + +function JacobiPolynomialBasis{D}( + ::Type{T}, order::Int, filter::Function=_q_filter) where {D,T} + + orders = tfill(order,Val{D}()) + JacobiPolynomialBasis{D}(T,orders,filter) +end + +# API + +function get_exponents(b::JacobiPolynomialBasis) + indexbase = 1 + [Tuple(t) .- indexbase for t in b.terms] +end + +function get_order(b::JacobiPolynomialBasis) + maximum(b.orders) +end + +function get_orders(b::JacobiPolynomialBasis) + b.orders +end + +return_type(::JacobiPolynomialBasis{D,T}) where {D,T} = T + +# Field implementation + +function return_cache(f::JacobiPolynomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} + @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::JacobiPolynomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} + 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_jp!(v,xi,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,JacobiPolynomialBasis{D,V}}, + x::AbstractVector{<:Point}) where {D,V} + + 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,JacobiPolynomialBasis{D,T}}, + x::AbstractVector{<:Point}) where {D,T} + + 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_jp!(v,xi,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,JacobiPolynomialBasis{D,V}}, + x::AbstractVector{<:Point}) where {D,V} + + 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,JacobiPolynomialBasis{D,T}}, + x::AbstractVector{<:Point}) where {D,T} + + 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_jp!(v,xi,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::JacobiPolynomialBasis{D,T},x::Point) where {D,T} + ndof = length(f.terms)*num_components(T) + r = CachedArray(zeros(T,(ndof,))) + xs = [x] + cf = return_cache(f,xs) + r, cf, xs +end + +function evaluate!(cache,f::JacobiPolynomialBasis{D,T},x::Point) where {D,T} + 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,JacobiPolynomialBasis{D,V}}, x::Point) where {N,D,V} + 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,JacobiPolynomialBasis{D,V}}, x::Point) where {N,D,V} + 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 + +function _evaluate_1d_jp!(v::AbstractMatrix{T},x,order,d) where T + n = order + 1 + z = one(T) + @inbounds v[d,1] = z + if n > 1 + ξ = ( 2*x[d] - 1 ) + for i in 2:n + @inbounds v[d,i] = sqrt(2*i-1)*jacobi(ξ,i-1,0,0) + end + end +end + +function _gradient_1d_jp!(v::AbstractMatrix{T},x,order,d) where T + n = order + 1 + z = zero(T) + @inbounds v[d,1] = z + if n > 1 + ξ = ( 2*x[d] - 1 ) + for i in 2:n + @inbounds v[d,i] = sqrt(2*i-1)*i*jacobi(ξ,i-2,1,1) + end + end +end + +function _hessian_1d_jp!(v::AbstractMatrix{T},x,order,d) where T + n = order + 1 + z = zero(T) + @inbounds v[d,1] = z + if n > 1 + @inbounds v[d,2] = z + ξ = ( 2*x[d] - 1 ) + for i in 3:n + @inbounds v[d,i] = sqrt(2*i-1)*(i*(i+1)/2)*jacobi(ξ,i-3,2,2) + end + end +end + +function _evaluate_nd_jp!( + v::AbstractVector{V}, + x, + orders, + terms::AbstractVector{CartesianIndex{D}}, + c::AbstractMatrix{T}) where {V,T,D} + + dim = D + for d in 1:dim + _evaluate_1d_jp!(c,x,orders[d],d) + end + + o = one(T) + k = 1 + + for ci in terms + + s = o + for d in 1:dim + @inbounds s *= c[d,ci[d]] + end + + k = _set_value!(v,s,k) + + end + +end + +function _gradient_nd_jp!( + v::AbstractVector{G}, + x, + orders, + terms::AbstractVector{CartesianIndex{D}}, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + ::Type{V}) where {G,T,D,V} + + dim = D + for d in 1:dim + _evaluate_1d_jp!(c,x,orders[d],d) + _gradient_1d_jp!(g,x,orders[d],d) + end + + z = zero(Mutable(VectorValue{D,T})) + o = one(T) + k = 1 + + for ci in terms + + 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!(v,s,k,V) + + end + +end + +function _hessian_nd_jp!( + v::AbstractVector{G}, + x, + orders, + terms::AbstractVector{CartesianIndex{D}}, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + h::AbstractMatrix{T}, + ::Type{V}) where {G,T,D,V} + + dim = D + for d in 1:dim + _evaluate_1d_jp!(c,x,orders[d],d) + _gradient_1d_jp!(g,x,orders[d],d) + _hessian_1d_jp!(h,x,orders[d],d) + end + + z = zero(Mutable(TensorValue{D,D,T})) + o = one(T) + k = 1 + + for ci in terms + + 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!(v,s,k,V) + + end + +end diff --git a/src/Polynomials/Polynomials.jl b/src/Polynomials/Polynomials.jl index 9b955a1eb..826e6f07f 100644 --- a/src/Polynomials/Polynomials.jl +++ b/src/Polynomials/Polynomials.jl @@ -25,6 +25,7 @@ export QGradMonomialBasis export QCurlGradMonomialBasis export PCurlGradMonomialBasis export ModalC0Basis +export JacobiPolynomialBasis export get_exponents export get_order @@ -41,4 +42,6 @@ include("PCurlGradMonomialBases.jl") include("ModalC0Bases.jl") +include("JacobiPolynomialBases.jl") + end # module diff --git a/test/PolynomialsTests/ModalC0BasesTests.jl b/test/PolynomialsTests/ModalC0BasesTests.jl index 577ba9737..f5e084633 100644 --- a/test/PolynomialsTests/ModalC0BasesTests.jl +++ b/test/PolynomialsTests/ModalC0BasesTests.jl @@ -17,7 +17,6 @@ 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) diff --git a/test/PolynomialsTests/runtests.jl b/test/PolynomialsTests/runtests.jl index eb14c265f..813a48417 100644 --- a/test/PolynomialsTests/runtests.jl +++ b/test/PolynomialsTests/runtests.jl @@ -12,6 +12,8 @@ using Test @testset "ModalC0Bases" begin include("ModalC0BasesTests.jl") end +@testset "JacobiPolynomialBases" begin include("JacobiPolynomialBasesTests.jl") end + #@testset "ChangeBasis" begin include("ChangeBasisTests.jl") end end # module From ea7456f331249a084fd21f58d194fcde5b2b70ff Mon Sep 17 00:00:00 2001 From: Eric Neiva Date: Fri, 31 Mar 2023 18:48:07 +0200 Subject: [PATCH 2/3] Added missing file --- .../JacobiPolynomialBasesTests.jl | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 test/PolynomialsTests/JacobiPolynomialBasesTests.jl diff --git a/test/PolynomialsTests/JacobiPolynomialBasesTests.jl b/test/PolynomialsTests/JacobiPolynomialBasesTests.jl new file mode 100644 index 000000000..fdbfe87fc --- /dev/null +++ b/test/PolynomialsTests/JacobiPolynomialBasesTests.jl @@ -0,0 +1,63 @@ +module JacobiPolynomialBasisTests + +using Test +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Fields: Broadcasting +using Gridap.Polynomials + +# 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 = 3 +b1 = JacobiPolynomialBasis{1}(V,order) +∇b1 = Broadcasting(∇)(b1) +∇∇b1 = Broadcasting(∇)(∇b1) + +@test evaluate(b1,[x1,x2,x3,]) ≈ [ 1.0 -1.7320508075688772 2.23606797749979 -2.6457513110645907; + 1.0 0.0 -1.118033988749895 -0.0; + 1.0 1.7320508075688772 2.23606797749979 2.6457513110645907 ] +@test evaluate(∇b1,[x1,x2,x3,]) ≈ G[ (0.0,) (3.4641016151377544,) (-13.416407864998739,) (31.74901573277509,); + (0.0,) (3.4641016151377544,) (0.0,) (-7.937253933193772,); + (0.0,) (3.4641016151377544,) (13.416407864998739,) (31.74901573277509,) ] +@test evaluate(∇∇b1,[x1,x2,x3,]) ≈ H[ (0.0,) (0.0,) (13.416407864998739,) (-79.37253933193772,); + (0.0,) (0.0,) (13.416407864998739,) (0.0,); + (0.0,) (0.0,) (13.416407864998739,) (79.37253933193772,) ] + +x1 = Point(0.0,0.0) +x2 = Point(0.5,0.5) +x3 = Point(1.0,1.0) +b2 = JacobiPolynomialBasis{2}(V,order) +∇b2 = Broadcasting(∇)(b2) +∇∇b2 = Broadcasting(∇)(∇b2) + +G = gradient_type(V,x1) +H = gradient_type(G,x1) + +@test evaluate(b2,[x1,x2,x3,]) ≈ [ 1.0 -1.7320508075688772 2.23606797749979 -2.6457513110645907 #= + =# -1.7320508075688772 2.9999999999999996 -3.872983346207417 #= + =# 4.58257569495584 2.23606797749979 -3.872983346207417 #= + =# 5.000000000000001 -5.916079783099617 -2.6457513110645907 #= + =# 4.58257569495584 -5.916079783099617 7.000000000000001; + 1.0 0.0 -1.118033988749895 -0.0 0.0 0.0 -0.0 -0.0 #= + =# -1.118033988749895 -0.0 1.2500000000000002 0.0 -0.0 -0.0 0.0 0.0; + 1.0 1.7320508075688772 2.23606797749979 2.6457513110645907 #= + =# 1.7320508075688772 2.9999999999999996 3.872983346207417 #= + =# 4.58257569495584 2.23606797749979 3.872983346207417 #= + =# 5.000000000000001 5.916079783099617 2.6457513110645907 #= + =# 4.58257569495584 5.916079783099617 7.000000000000001 ] +@test evaluate(∇b2,[x1,x2,x3,])[:,10] ≈ G[ (7.745966692414834, 23.2379000772445); + (-3.872983346207417, 0.0); + (7.745966692414834, 23.2379000772445) ] +@test evaluate(∇∇b2,[x1,x2,x3,])[:,10] ≈ H[ (0.0, -46.475800154489, -46.475800154489, -23.2379000772445); + (-0.0, 0.0, 0.0, 0.0); + (0.0, 46.475800154489, 46.475800154489, 23.2379000772445) ] + +end # module From 616aa7ff395eae6546b05f62ee595e51f1959630 Mon Sep 17 00:00:00 2001 From: Eric Neiva Date: Fri, 31 Mar 2023 19:56:50 +0200 Subject: [PATCH 3/3] Update NEWS.md --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index c29cc23f1..d5caacca6 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Jacobi polynomial bases. Since PR [#896](https://github.com/gridap/Gridap.jl/pull/896). + ### Fixed - Fixed the method `get_normal_vector` for `AdaptedTriangulation`. The method `get_facet_normal`