diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index 72f55e014..64bc378ea 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: version: - - '1.9' # Needs to be lowest supported version + - '1.10' # Needs to be lowest supported version os: - ubuntu-latest arch: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6175ea136..ab7ed280f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,6 @@ jobs: fail-fast: false matrix: # Main tests for linux version: - - '1.9' - '1.10' - '1.11' os: diff --git a/NEWS.md b/NEWS.md index c165131ea..eebd670ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,73 @@ 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). +## Moment-based changes + +### Added + +- Documentation and refactoring of `Gridap.Polynomials`. Since PR[#1072](https://github.com/gridap/Gridap.jl/pull/#1072). +- Two new families of polynomial bases in addition to `Monomial`, `Legendre` (former `Jacobi`) and `ModalC0`: `Chebyshev` and `Bernstein` +- `MonomialBasis` and `Q[Curl]GradMonomialBasis` have been generalized to `Legendre`, `Chebyshev` and `Bernstein` using the new `CartProdPolyBasis` and `CompWiseTensorPolyBasis` respectively. +- `PCurlGradMonomialBasis` has been generalized to `Legendre` and `Chebyshev` using the new `RaviartThomasPolyBasis`. +- New aliases and high level constructor for `CartProdPolyBasis` (former MonomialBasis): `MonomialBasis`, `LegendreBasis`, `ChebyshevBasis` and `BernsteinBasis`. +- New high level factory `FEEC_poly_basis` for the bases for the scalar Lagrange, Nedelec, Raviart-Thomas, BDM spaces, and all other spaces of the Periodic Table of the Finite Elements. (for Serendipity, only scalar is supported). For example: + - Nedelec on simplex `FEEC_poly_basis(Val(D),Float64,order+1, 1,:P⁻)` + - Nedelec on n-cubes `FEEC_poly_basis(Val(D),Float64,order+1, 1,:Q⁻)` + - Raviart on simplex `FEEC_poly_basis(Val(D),Float64,order+1,D-1,:P⁻; rotate_90=(D==2))` + - Raviart on n-cubes `FEEC_poly_basis(Val(D),Float64,order+1,D-1,:Q⁻; rotate_90=(D==2))` + - BDM on simplex `FEEC_poly_basis(Val(D),Float64,order+1,D-1,:P ; rotate_90=(D==2))` +- Added `BernsteinBasisOnSimplex` that implements Bernstein polynomials in barycentric coordinates, since PR[#1104](https://github.com/gridap/Gridap.jl/pull/#1104). +- More documentation of `Gridap.ReferenceFEs`. Since PR[#1109](https://github.com/gridap/Gridap.jl/pull/#1109). + +- Some refactoring of `Gridap.TensorValues` to simplify maintenance and new implementations. Since PR[#1115](https://github.com/gridap/Gridap.jl/pull/#1115). + - Added `SkewSymTensorValue`: a new `<:MultiValue` 2nd order tensor type such that `transpose(s)==-s`. + - `congruent_prod`: new operation for 2nd order tensors: `a,b -> bᵀ⋅a⋅b` preserving symmetry of `a`. + - `component_basis` and `representatives_of_componentbasis_dual`: new APIs for `::MultiValue`s yielding bases of the vector space spanned by the independent components of a tensor type (1st method) and its dual space (2nd method). + +- Refactoring of moment-based ReferenceFEs, those using face-integral linear forms for DoFs, including `RaviartThomas`, `Nedelec`, `BDM` and `CrouzeixRaviart`. Since PR[#1048](https://github.com/gridap/Gridap.jl/pull/#1048). + - The mid-level `MomentBasedRefFE` factory function creates moment based refFEs + - The low-level `FaceMeasure` implements the numerical integration of a bilinear integrand over the faces of a polytope. + - The low-level `MomentBasedDofBasis` implements a discretized basis of moment DoF +- Implemented moment-based scalar (`H1` conform) elements for scalar elements `lagrangian` and `serendipity` under the names `modal_lagrangian` and `modal_serendipity`. They are the default elements when calling for `k`=0-forms in the generic FEEC reference FE constructor (`P⁻`/`:P`/`:Q⁻` => `lagrangian`, `:S`=> `serendipity`), use the keyword `nodal=true` to opt-in nodal DOF based counterpart. Since PR[#1173](https://github.com/gridap/Gridap.jl/pull/1173). +- Unified the high-level constructors of ReferenceFEs +- New high level `ReferenceFE`s constructor using Arnold et al FEEC notations (Periodic Table of the Finite Elements): `ReferenceFE(F::Symbol, r, k, [, T::Type]; kwargs...)` with `F` the element family, `r` polynomial order and `k` the form order. +- Added `poly_type`, `mom_poly_type` and `change_dof` keyword arguments to many low and high-level reference FE constructors, they enable to control the polynomial (pre-)bases choice for the approximation space and moment test-spaces of the reference elements (See `ReferenceFEs` doc. page). Since PR[#1173](https://github.com/gridap/Gridap.jl/pull/1173). +- Documented the implemented ReferenceFEs with available order and other information in the `ReferenceFEs` section of the doc. +- Implemented the Nedelec reference elements of the second kind `nedelec2`. +- API for Geometric decomposition of polynomial bases, implemented for simplices and n-cubes. Since PR[#1144](https://github.com/gridap/Gridap.jl/pull/1144). + The geometric decomposition API consist in the methods `has_geometric_decomposition`, `get_face_own_funs` and `get_facet_flux_sign_flip`. + +### Fixed + +- Fixed evaluation of `LinearCombinationDofVector` on vector of `<:Field`s (only impacts ModalC0 FEs and future moment based reffes)., since PR[#1105](https://github.com/gridap/Gridap.jl/pull/#1105). +- Minor `MuliValue` bugfixes for `isless` and `<=` with scalars. + +### Changed + +- Existing Jacobi polynomial bases/spaces were renamed to Legendre (which they were). +- `Monomial` is now subtype of the new abstract type`Polynomial <: Field` +- `MonomialBasis` is now an alias for `CartProdPolyBasis{...,Monomial}` +- All polynomial bases are now subtypes of the new abstract type `PolynomialBasis <: AbstractVector{<:Polynomial}` +- `get_order`, now always returns the maximum order of the basis, the correspondence with `ReferenceFEs` constructors is summarized in the documentation of the module. +- `NedelecPreBasisOnSimplex` is renamed `NedelecPolyBasisOnSimplex` +- `JacobiPolynomial` is renamed `Legendre` and subtypes `Polynomial` +- `JacobiPolynomialBasis` is renamed `LegendreBasis` +- `ModalC0BasisFunction` is renamed `ModalC0` and subtypes `Polynomial` +- On simplices, the default polynomial bases of BDM, Nédélec and Raviart-Thomas RefFEs have changed for barycentric polynomial bases which lead to better conditioned systems for higher order. +- Similarly, on n-cubes, the default polynomial bases of BDM, Nédélec and Raviart-Thomas RefFEs have changed for Legendre (tensor-product) polynomial bases instead of Monomials. +- Changed `Base.==` for `ReferenceFE`s, and implemented it for `LinearCombinationFieldVector` and `LinearCombinationDofVector`. The implementation now uses AutoHashEquals.jl, so RefFEs are now only equal if they have the same prebasis, shapefun and dofs. +- Monomial (pre)bases have been replaced with Bernstein / Barycentric bases for non-scalar finite elements on simplices. + +### Deprecated + +- `num_terms(f::AbstractVector{<:Field})` in favor of `length(f::PolynomialBasis)` +- `MonomialBasis{D}(args...)` in favor of `MonomialBasis(Val(D), args...)` +- `[P/Q][Curl]GradMonomialBasis{D}(args...)` in favor of `FEEC_poly_basis` +- `NedelecPreBasisOnSimplex{D}(order)` in favor of `NedelecPolyBasisOnSimplex(Val(D), Float64, order)` +- `JacobiPolynomialBasis{D}(args...)` in favor of `LegendreBasis(Val(D), args...)` + +### Removed + ## [0.19.5] - 2025-09-19 ### Added diff --git a/Project.toml b/Project.toml index 9a2a27b4b..fa79c0634 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "0.19.5" [deps] AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +AutoHashEquals = "15f4f7f2-30c1-5605-9d31-71845cf9641f" BSON = "fbb218c0-5317-5bc6-957e-2ee96dd4b1f0" BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" @@ -40,6 +41,7 @@ TikzPicturesExt = "TikzPictures" [compat] AbstractTrees = "0.3.3, 0.4" Aqua = "0.8" +AutoHashEquals = "2.2.0" BSON = "0.3.4" BlockArrays = "1" Combinatorics = "1" @@ -66,7 +68,7 @@ Statistics = "1" Test = "1" TikzPictures = "3" WriteVTK = "1.21.1" -julia = "1.9" +julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/docs/Project.toml b/docs/Project.toml index 44ea2eafb..90b71636c 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,8 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Gridap = "56d4f2e9-7ea1-5844-9cf6-b9c51ca7ce8e" +Kroki = "b3565e16-c1f2-4fe9-b4ab-221c88942068" +TikzPictures = "37f6aa50-8035-52d0-81c2-5a1d08754b2d" [compat] Documenter = "1.0" diff --git a/docs/generate_diagrams.jl b/docs/generate_diagrams.jl new file mode 100644 index 000000000..ba9409389 --- /dev/null +++ b/docs/generate_diagrams.jl @@ -0,0 +1,102 @@ +using Kroki + +macro export_kroki(name, str) + return quote + write("src/assets/"*string($name)*".svg", render($str, "svg")) + end +end + +# Plantuml uml diagrams syntax: https://plantuml.com/class-diagram#49b7759afaffc066 + +@export_kroki :poly_1 plantuml""" + @startuml + skinparam groupInheritance 2 + hide empty members + + together { + abstract Field + abstract Polynomial { + +isHierarchical + } + } + Field <|-left- Polynomial + + together { + struct Monomial + struct Legendre + struct Chebyshev + struct ModalC0 + struct Bernstein + } + + Polynomial <|.. Monomial + Polynomial <|.. Legendre + Polynomial <|.. Chebyshev + Polynomial <|.. ModalC0 + Polynomial <|.. Bernstein + + @enduml + """ + +@export_kroki :poly_2 plantuml""" + @startuml + skinparam groupInheritance 2 + hide empty members + + together { + abstract "AbstractArray{<:Polynomial}" as a1 + abstract PolynomialBasis { + +get_order + +return_type + } + } + a1 <|-left- PolynomialBasis + + together { + struct CartProdPolyBasis { + +get_exponents + +get_orders + } + struct CompWiseTensorPolyBasis + struct RaviartThomasPolyBasis + struct NedelecPolyBasisOnSimplex + struct BernsteinBasisOnSimplex { + +get_orders + } + struct ModalC0Basis { + +get_orders + } + } + + struct BarycentricPΛBasis { + +print_indices + } + struct BarycentricPmΛBasis { + +print_indices + } + + PolynomialBasis <|.. CartProdPolyBasis + PolynomialBasis <|.. CompWiseTensorPolyBasis + PolynomialBasis <|.. RaviartThomasPolyBasis + PolynomialBasis <|.. NedelecPolyBasisOnSimplex + PolynomialBasis <|.. BernsteinBasisOnSimplex + PolynomialBasis <|.. ModalC0Basis + PolynomialBasis <|.. BarycentricPmΛBasis + PolynomialBasis <|.. BarycentricPΛBasis + + object "(<:Polynomial)Basis" as m1 + object "FEEC_poly_basis" as m2 + + CartProdPolyBasis <-- m1 + BarycentricPmΛBasis o-- BernsteinBasisOnSimplex + BarycentricPΛBasis o-- BernsteinBasisOnSimplex + CartProdPolyBasis <-- m2 + CompWiseTensorPolyBasis <-- m2 + RaviartThomasPolyBasis <-- m2 + NedelecPolyBasisOnSimplex <-- m2 + BarycentricPmΛBasis <-- m2 + BarycentricPΛBasis <-- m2 + + @enduml + """ + diff --git a/docs/generate_tikz.jl b/docs/generate_tikz.jl new file mode 100644 index 000000000..cda7e35a7 --- /dev/null +++ b/docs/generate_tikz.jl @@ -0,0 +1,28 @@ + +using Gridap +using TikzPictures + +using Gridap.ReferenceFEs, Gridap.Adaptivity + +function export_tikz_poly(name, p, d, label_offset) + options = "background rectangle/.style={draw=white,fill=white,rounded corners=1ex}, show background rectangle, color=black, scale=2" + preamble = "\\usetikzlibrary {backgrounds}" + tp = TikzPicture(p;options,preamble,label_dim=d,label_offset) + TikzPictures.save(SVG(joinpath("src/assets/polytopes/",name)), tp) +end + +export_tikz_poly("SEGMENT_0", Polygon([Point(0.0,0.0),Point(1.0,0.0)]), 0, Point(0.0,0.1)) + +for d in 0:1 + offset = iszero(d) ? 0.1 : 0.0 + export_tikz_poly("QUAD_$d", QUAD, d, offset) + export_tikz_poly("TRI_$d" , TRI, d, offset) +end + +for d in 0:2 + offset = iszero(d) ? 0.1 : 0.0 + export_tikz_poly("HEX_$d", HEX, d, offset) + export_tikz_poly("TET_$d" , TET, d, offset) + export_tikz_poly("PYRAMID_$d", PYRAMID, d, offset) + export_tikz_poly("WEDGE_$d", WEDGE, d, offset) +end diff --git a/docs/make.jl b/docs/make.jl index eda6f02dd..947e0f6c9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,28 +1,37 @@ using Documenter using Gridap +using TikzPictures pages = [ "Home" => "index.md", "Getting Started" => "getting-started.md", - "Gridap" => "Gridap.md", - "Gridap.Helpers" => "Helpers.md", - "Gridap.Io" => "Io.md", - "Gridap.Algebra" => "Algebra.md", - "Gridap.Arrays" => "Arrays.md", - "Gridap.TensorValues" => "TensorValues.md", - "Gridap.Fields" => "Fields.md", - "Gridap.Polynomials" => "Polynomials.md", - "Gridap.ReferenceFEs" => "ReferenceFEs.md", - "Gridap.Geometry" => "Geometry.md", - "Gridap.CellData" => "CellData.md", - "Gridap.Visualization" => "Visualization.md", - "Gridap.FESpaces" => "FESpaces.md", - "Gridap.MultiField" => "MultiField.md", - "Gridap.ODEs" => "ODEs.md", - "Gridap.Adaptivity" => "Adaptivity.md", + "Gridap at a glance" => "overview.md", + "Ecosystem" => "ecosystem.md", + "Modules" => [ + "Helpers" => "modules/Helpers.md", + "Io" => "modules/Io.md", + "Algebra" => "modules/Algebra.md", + "Arrays" => "modules/Arrays.md", + "TensorValues" => "modules/TensorValues.md", + "Fields" => "modules/Fields.md", + "Polynomials" => "modules/Polynomials.md", + "ReferenceFEs" => "modules/ReferenceFEs.md", + "Geometry" => "modules/Geometry.md", + "CellData" => "modules/CellData.md", + "Visualization" => "modules/Visualization.md", + "FESpaces" => "modules/FESpaces.md", + "MultiField" => "modules/MultiField.md", + "ODEs" => "modules/ODEs.md", + "Adaptivity" => "modules/Adaptivity.md", + ], + "Extensions" => [ + "TikzPictures" => "extensions/TikzPictures.md", + ], "Developper notes" => Any[ "dev-notes/block-assemblers.md", + "dev-notes/pullbacks.md", + "dev-notes/bernstein.md", "dev-notes/autodiff.md", ], ] @@ -30,12 +39,13 @@ pages = [ makedocs( sitename = "Gridap.jl", format = Documenter.HTML( - size_threshold=nothing + size_threshold=nothing, + size_threshold_warn=300 * 2^10 # 300KiB ), modules = [Gridap], pages = pages, doctest = false, - warnonly = [:cross_references,:missing_docs], + warnonly = [:missing_docs,:cross_references], # ,:cross_references checkdocs = :exports, ) diff --git a/docs/src/Arrays.md b/docs/src/Arrays.md deleted file mode 100644 index 459617ce3..000000000 --- a/docs/src/Arrays.md +++ /dev/null @@ -1,10 +0,0 @@ - -```@meta -CurrentModule = Gridap.Arrays -``` -# Gridap.Arrays - -```@autodocs -Modules = [Arrays,] -``` - diff --git a/docs/src/Gridap.md b/docs/src/Gridap.md deleted file mode 100644 index 4463a487c..000000000 --- a/docs/src/Gridap.md +++ /dev/null @@ -1,5 +0,0 @@ -# Gridap - -```@docs -Gridap -``` diff --git a/docs/src/Polynomials.md b/docs/src/Polynomials.md deleted file mode 100644 index 33a3e658c..000000000 --- a/docs/src/Polynomials.md +++ /dev/null @@ -1,10 +0,0 @@ - -```@meta -CurrentModule = Gridap.Polynomials -``` - -# Gridap.Polynomials - -```@autodocs -Modules = [Polynomials,] -``` diff --git a/docs/src/ReferenceFEs.md b/docs/src/ReferenceFEs.md deleted file mode 100644 index 430b3cb4f..000000000 --- a/docs/src/ReferenceFEs.md +++ /dev/null @@ -1,10 +0,0 @@ -```@meta -CurrentModule = Gridap.ReferenceFEs -``` - -# Gridap.ReferenceFEs - -```@autodocs -Modules = [ReferenceFEs,] -``` - diff --git a/docs/src/assets/poly_1.svg b/docs/src/assets/poly_1.svg new file mode 100644 index 000000000..25b0e93e9 --- /dev/null +++ b/docs/src/assets/poly_1.svg @@ -0,0 +1 @@ +FieldPolynomialisHierarchicalMonomialLegendreChebyshevModalC0Bernstein \ No newline at end of file diff --git a/docs/src/assets/poly_2.svg b/docs/src/assets/poly_2.svg new file mode 100644 index 000000000..35e2299bf --- /dev/null +++ b/docs/src/assets/poly_2.svg @@ -0,0 +1 @@ +AbstractArray{<:Polynomial}PolynomialBasisget_orderreturn_typeCartProdPolyBasisget_exponentsget_ordersCompWiseTensorPolyBasisRaviartThomasPolyBasisNedelecPolyBasisOnSimplexBernsteinBasisOnSimplexget_ordersModalC0Basisget_ordersBarycentricPΛBasisprint_indicesBarycentricPmΛBasisprint_indices(<:Polynomial)BasisFEEC_poly_basis \ No newline at end of file diff --git a/docs/src/assets/polytopes/HEX_0.svg b/docs/src/assets/polytopes/HEX_0.svg new file mode 100644 index 000000000..2d0ea2722 --- /dev/null +++ b/docs/src/assets/polytopes/HEX_0.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/HEX_1.svg b/docs/src/assets/polytopes/HEX_1.svg new file mode 100644 index 000000000..aeaa08d7d --- /dev/null +++ b/docs/src/assets/polytopes/HEX_1.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/HEX_2.svg b/docs/src/assets/polytopes/HEX_2.svg new file mode 100644 index 000000000..2a4062bdd --- /dev/null +++ b/docs/src/assets/polytopes/HEX_2.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/PYRAMID_0.svg b/docs/src/assets/polytopes/PYRAMID_0.svg new file mode 100644 index 000000000..fe7a1748b --- /dev/null +++ b/docs/src/assets/polytopes/PYRAMID_0.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/PYRAMID_1.svg b/docs/src/assets/polytopes/PYRAMID_1.svg new file mode 100644 index 000000000..6aee56455 --- /dev/null +++ b/docs/src/assets/polytopes/PYRAMID_1.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/PYRAMID_2.svg b/docs/src/assets/polytopes/PYRAMID_2.svg new file mode 100644 index 000000000..620adc794 --- /dev/null +++ b/docs/src/assets/polytopes/PYRAMID_2.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/QUAD_0.svg b/docs/src/assets/polytopes/QUAD_0.svg new file mode 100644 index 000000000..5137847b1 --- /dev/null +++ b/docs/src/assets/polytopes/QUAD_0.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/QUAD_1.svg b/docs/src/assets/polytopes/QUAD_1.svg new file mode 100644 index 000000000..c3180603f --- /dev/null +++ b/docs/src/assets/polytopes/QUAD_1.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/SEGMENT_0.svg b/docs/src/assets/polytopes/SEGMENT_0.svg new file mode 100644 index 000000000..4e68eeef9 --- /dev/null +++ b/docs/src/assets/polytopes/SEGMENT_0.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/TET_0.svg b/docs/src/assets/polytopes/TET_0.svg new file mode 100644 index 000000000..40364231c --- /dev/null +++ b/docs/src/assets/polytopes/TET_0.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/TET_1.svg b/docs/src/assets/polytopes/TET_1.svg new file mode 100644 index 000000000..997b43e5f --- /dev/null +++ b/docs/src/assets/polytopes/TET_1.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/TET_2.svg b/docs/src/assets/polytopes/TET_2.svg new file mode 100644 index 000000000..0823e6c88 --- /dev/null +++ b/docs/src/assets/polytopes/TET_2.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/TRI_0.svg b/docs/src/assets/polytopes/TRI_0.svg new file mode 100644 index 000000000..88c2ae49a --- /dev/null +++ b/docs/src/assets/polytopes/TRI_0.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/TRI_1.svg b/docs/src/assets/polytopes/TRI_1.svg new file mode 100644 index 000000000..dea59ace5 --- /dev/null +++ b/docs/src/assets/polytopes/TRI_1.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/WEDGE_0.svg b/docs/src/assets/polytopes/WEDGE_0.svg new file mode 100644 index 000000000..0712f05ee --- /dev/null +++ b/docs/src/assets/polytopes/WEDGE_0.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/WEDGE_1.svg b/docs/src/assets/polytopes/WEDGE_1.svg new file mode 100644 index 000000000..c88068198 --- /dev/null +++ b/docs/src/assets/polytopes/WEDGE_1.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/polytopes/WEDGE_2.svg b/docs/src/assets/polytopes/WEDGE_2.svg new file mode 100644 index 000000000..795bb1834 --- /dev/null +++ b/docs/src/assets/polytopes/WEDGE_2.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/dev-notes/bernstein.md b/docs/src/dev-notes/bernstein.md new file mode 100644 index 000000000..208fd7c69 --- /dev/null +++ b/docs/src/dev-notes/bernstein.md @@ -0,0 +1,589 @@ +```@meta +CurrentModule = Gridap.Polynomials +``` + +# Bernstein bases algorithms + +## Barycentric coordinates + +A ``D``-dimensional simplex ``T`` is defined by ``N=D+1`` vertices ``\{v_1, +v_2, …, v_N\}=\{v_i\}_{i∈1:N}``. The barycentric coordinates +``λ(\bm{x})=\{λ_j(\bm{x})\}_{1 ≤ j ≤ N}`` are uniquely +defined by: +```math +\bm{x} = ∑_{1 ≤ j ≤ N} λ_j(\bm{x})v_j \quad\text{and}\quad +∑_{1≤ j≤ N} λ_j(\bm{x}) = 1, +``` +as long as the simplex is non-degenerate (vertices are not all in one +hyperplane). + +Assuming the simplex polytopal (has flat faces), this change of coordinates is +affine, and is implemented using: +```math +λ(\bm{x}) = M\left(\begin{array}{c} 1\\ x_1\\ ⋮\\ x_D \end{array}\right) +\quad\text{with}\quad M = +\left(\begin{array}{cccc} +1 & 1 & ⋯ & 1 \\ +(v_1)_1 & (v_2)_1 & ⋯ & (v_N)_1 \\ +⋮ & ⋮ & ⋯ & ⋮ \\ +(v_1)_D & (v_2)_D & ⋯ & (v_N)_D \\ +\end{array}\right)^{-1} +``` +where the inverse exists because ``T`` is non-degenerate [1], cf. functions +[`_cart_to_bary`](@ref) and [`_compute_cart_to_bary_matrix`](@ref). Additionally, +we have ``∂_{x_i} λ_j(\bm{x}) = M_{j,i+1}``, so +```math +∇ λ_j = M_{2:N, j}. +``` +The matrix ``M`` is all we need that depends on ``T`` in order to compute +Bernstein polynomials and their derivatives, it is stored in the field +`cart_to_bary_matrix` of [`BernsteinBasisOnSimplex`](@ref), when ``T`` is not +the reference simplex. + +On the reference simplex defined by the vertices `get_vertex_coordinates(SEGMENT / TRI / TET⋯)`: +```math +\begin{aligned} +v_1 & = (0\ 0\ ⋯\ 0), \\ +v_2 & = (0\ 1\ ⋯\ 0), \\ +⋮ & \\ +v_N & = (0\ ⋯\ 0\ 1), +\end{aligned} +``` +the matrix ``M`` is not stored because +```math +λ(\bm{x}) = \Big(1-∑_{1≤ i≤ D} x_i, x_1, x_2, ⋯, x_D\Big) +\quad\text{and}\quad +∂_{x_i} λ_j = δ_{i+1,j} - δ_{1j} = M_{j,i+1}. +``` + +## Bernstein polynomials definition + +The univariate [`Bernstein`](@ref) polynomials forming a basis of ``ℙ_K`` +are defined by +```math +B^K_{n}(x) = \binom{K}{n} x^n (1-x)^{K-n}\qquad\text{ for } 0≤ n≤ K. +``` + +The ``D``-multivariate Bernstein polynomials of degree ``K`` relative to a +simplex ``T`` are defined by +```math +B^{D,K}_α(\bm{x}) = \binom{K}{α} λ(\bm{x})^α\qquad\text{for all }α ∈\mathcal{I}_K^D +``` +where +- ``\mathcal{I}_K^D = \{\ α∈(\mathbb{Z}_+)^{D+1} \quad|\quad |α|=K\ \}`` +- ``|α|=∑_{1≤ i≤ N} α_i`` +- ``\binom{K}{α} = \frac{K!}{α_1 !α_2 !… α_N!}`` +- ``λ`` are the barycentric coordinates relative to ``T`` (defined above) + +The superscript ``D`` and ``K`` in ``B^{D,K}_α(x)`` can be omitted because they +are always determined by ``α`` using ``{D=\#(α)-1}`` and ``K=|α|``. The set +``\{B_α\}_{α∈\mathcal{I}_K^D}`` is a basis of ``ℙ^D_K``, implemented by +[`BernsteinBasisOnSimplex`](@ref). + +### Bernstein indices and indexing + +Working with Bernstein polynomials requires dealing with several quantities +indexed by some ``α ∈ \mathcal{I}_K^D``, the polynomials themselves but also the +coefficients ``c_α`` of a polynomial in the basis, the domain points +``{\bm{x}_α = \underset{1≤i≤N}{∑} α_i v_i}`` and the intermediate +coefficients used in the de Casteljau algorithm. + +These indices are returned by [`bernstein_terms(K,D)`](@ref bernstein_terms). +When storing such quantities in arrays, ``∙_α`` is stored at index +[`bernstein_term_id(α)`](@ref bernstein_term_id), which is the index of `α` +in `bernstein_terms(sum(α),length(α)-1)`. + +We adopt the convention that a quantity indexed by a ``α ∉ ℤ_+^N`` is equal to +zero (to simplify the definition of algorithms where ``α=β-e_i`` appears). + +### The de Casteljau algorithms + +A polynomial ``p ∈ ℙ^D_K`` in Bernstein form ``p = ∑_{α∈\mathcal{I}^D_K}\, p_α +B_α`` can be evaluated at ``\bm{x}`` using the de Casteljau algorithms +[1, Algo. 2.9] by iteratively computing +```math +\qquad p_β^{(l)} = \underset{1 ≤ i ≤ N}{∑} λ_i\, p_{β+e_i}^{(l-1)} \qquad ∀β ∈ \mathcal{I}^D_{K-l}, +``` +for ``l=1, 2, …, K`` where ``p_α^{(0)}=p_α``, ``λ=λ(\bm{x})`` and the +result is ``p(\bm{x})=p_𝟎^{(K)}``. This algorithm is implemented (in +place) by [`_de_Casteljau_nD!`](@ref Polynomials._de_Casteljau_nD!). + +But Gridap implements the polynomial bases themselves instead of individual +polynomials in a basis. To compute all ``B_α`` at ``\bm{x}``, one can +use the de Casteljau algorithm going "downwards" (from the tip of the pyramid +to the base). The idea is to use the relation +```math +B_α = ∑_{1 ≤ i ≤ N} λ_i B_{α-e_i}\qquad ∀α ∈ ℤ_+^N,\ |α|≥1. +``` + +Starting from ``b_𝟎^{(0)}=B_𝟎(\bm{x})=1``, compute iteratively +```math +\qquad b_β^{(l)} = \underset{1 ≤ i ≤ N}{∑} λ_i\, b_{β-e_i}^{(l-1)} \qquad ∀β ∈ \mathcal{I}^D_{l}, +``` +for ``l=1,2, …, K``, where again ``λ=λ(\bm{x})`` and the result is +``B_α(\bm{x})=b_α^{(K)}`` for all ``α`` in ``\mathcal{I}^D_K``. This +algorithm is implemented (in place) by [`_downwards_de_Casteljau_nD!`](@ref). +The implementation is a bit tricky, because the iterations must be done in +reverse order to avoid erasing coefficients needed later, and a lot of summands +disappear (when ``(β-e_i)_i < 0``). + +The gradient and hessian of the `BernsteinBasisOnSimplex` are also implemented. +They rely on the following +```math +∂_q B_α(\bm{x}) = K\!∑_{1 ≤ i ≤ N} ∂_qλ_i\, B_{α-e_i}(\bm{x}),\qquad +∂_t ∂_q B_α(\bm{x}) = K\!∑_{1 ≤ i,j ≤ N} ∂_tλ_j\, ∂_qλ_i\, B_{α-e_i-e_j}(\bm{x}). +``` +The gradient formula comes from [1, Eq. (2.28)], and the second is derived from +the first using the fact that ``∂_qλ`` is homogeneous. The implementation of +the gradient and hessian compute the ``B_β`` using +`_downwards_de_Casteljau_nD!` up to order ``K-1`` and ``K-2`` respectively, and +then the results are assembled by `_grad_Bα_from_Bαm!` and +`_hess_Bα_from_Bαmm!` respectively. The implementation makes sure to only +access each relevant ``B_β`` once per ``(∇/H)B_α`` computed. Also, on the +reference simplex, the barycentric coordinates derivatives are computed at +compile time using ``∂_qλ_i = δ_{i q}-δ_{i N}``. + +## Bernstein basis generalization for ``ℙΛ`` spaces + +The [`BarycentricPmΛBasis`](@ref) and [`BarycentricPΛBasis`](@ref) bases respectively +implement the polynomial bases for the spaces ``ℙ_r^-Λ^k(T^D)`` and +``ℙ_rΛ^k(T^D)`` (we write ``ℙ_r^{(-)}Λ^k`` for either one of them) derived in +[2] on simplices of any dimension, for any form degree ``k`` and polynomial +degree ``r``. These spaces include and generalize several standard FE +polynomial spaces, see the Periodic Table of the Finite Elements [3]. + +The following notes explain the implementation in detail. For the moment, only +the space with form order ``{k = 0,1,D-1}`` and ``D`` are available, because the +forms are translated into their [vector calculus proxy](@ref "Translation between forms and vectors"). + +#### Face and form coefficients indexing + +Again, a ``D``-dimensional simplex ``T`` is defined by ``N=D+1`` vertices +``\{v_1, v_2, ..., v_N\}=\{v_i\}_{i∈1:N}``. We uniquely identify a +``d``-dimensional face ``F`` of ``T`` by the set of the ``d+1`` +increasing indices of its vertices: +```math +F = \{F_1, F_2, ..., F_{d+1}\} +\qquad\text{such that } 1≤ F_1 < F_2 < ... s,\, J_i-1∉I\}``, the number of columns of zeros of ``\hat{M}_{IJ}``. + +Then it can be shown that ``k-n`` is the rank of ``\hat{M}_{IJ}``, and that +```math +\hat{m}_I^J = \mathrm{det}(\hat{M}_{IJ}) = (-1)^{p(I,J)}δ_0^{n(I,J)}, +``` +where the dependency of ``p,n`` on ``I,J`` is made explicit, so in ``\hat{T}``, +there is +```math +ω̄_{I}^{α,J} = B_α \sum_{1≤l≤k+1} (-1)^{l+1} λ_{J(l)} \, \hat{m}_I^{J\backslash l}. +``` + +##### Coefficients ``\hat{ψ}_I^{α,J}`` + +The expression of ``ψ_{i}^{α,F,j}`` in ``\hat{T}`` is +```math +\hat{ψ}_{i}^{α,F,j} = M_{j,i+1} - \frac{α_j}{|α|}\sum_{l∈F}δ_{i+1,l} - δ_{1l} += M_{j,i+1} + \big( δ_{1,F_0}-\sum_{l∈F}δ_{i+1,l} \big)\frac{α_j}{|α|} +``` +leading to +```math +\hat{ψ}_I^{α,J} = \mathrm{det}\Big(\hat{M}_{IJ} + u\,v^{\intercal}\Big) +\quad\text{where}\quad +u^i = δ_{1,F(0)}-\sum_{l∈F}δ_{I(i)+1,\,l}, \qquad v^j = \frac{α_{J(j)}}{|α|}. +``` +We can use the following matrix determinant lemma: +```math +\mathrm{det}(\hat{M}_{IJ} + uv^\intercal) = +\mathrm{det}(\hat{M}_{IJ}) + v^\intercal\mathrm{adj}(\hat{M}_{IJ})u. +``` +The determinant ``\mathrm{det}(\hat{M}_{IJ})=\hat{m}_I^{J}`` was computed +above, but ``\mathrm{adj}(\hat{M}_{IJ})``, the transpose of the cofactor matrix +of ``\hat{M}_{IJ}``, is also needed. Let ``s=δ_1^{J_1}``, ``n`` and ``p`` be +defined as above, and additionally define +- ``q = \text{min } \{j\,|\,j>p,\ I_j+1 ∉J\}``, the index of the second row of ``\hat{M}_{IJ}`` containing no ``1`` (``q=0`` if there isn't any), +- ``m = \text{min } \{i\,|\,i>s,\ J_i-1 ∉I\}``, the index of the first column of ``\hat{M}_{IJ}`` containing only zeros (``m=0`` if there isn't any). + +Then the following table gives the required information to apply the matrix +determinant lemma and formulas for ``\hat{ψ}_I^{α,J}`` +```math +\begin{array}{|c|c|c|c|c|} +\hline +s & n & \mathrm{rank}\hat{M}_{IJ} & \mathrm{adj}\hat{M}_{IJ} & \hat{ψ}_I^{α,J} \\ +\hline +\hline +0 & 0 & k & δ_{ij} & 1 + u \cdot v\\ +\hline +0 & 1 & k-1 & (-1)^{m+p}δ_i^m δ^p_j & (-1)^{m+p}v^m u^p \\ +\hline +1 & 0 & k & (-1)^p(δ_{J(i),\,I(j)+1}-δ_{p,J(j)})& (-1)^p(1-u^p|v|+\underset{1≤ l In the special case where the implicit part is linear and the explicit part is quasilinear or semilinear, we could, in theory, identify two linear forms for the global residual. However, introducing this difference would call for an order-dependent classification of ODEs and this would create (infinitely) many new types. Since numerical schemes can rarely take advantage of this extra structure in practice, we still say that the global residual is semilinear in these cases. -## Classification of numerical schemes +### Classification of numerical schemes + We introduce a classification of numerical schemes based on where they evaluate the residual during the state update. * If it is possible (up to a change of variables) to write the system of equations for the state update as evaluations of the residual at known values (that depend on the solution at the current time) for all but the highest-order derivative, we say that the scheme is explicit. @@ -95,7 +98,8 @@ We introduce a classification of numerical schemes based on where they evaluate > ``` > where ``\boldsymbol{x}`` and the unknown of the state update. The scheme is explicit if it is possible to introduce a change of variables such that ``\boldsymbol{u}_{k}`` does not depend on ``\boldsymbol{x}``. Otherwise, it is implicit. -## Classification of systems of equations +### Classification of systems of equations + It is advantageous to introduce this classification of ODE and numerical schemes because the system of equations arising from the discretisation of the ODE by a numerical scheme will be linear or nonlinear depending on whether the scheme is explicit, implicit, or implicit-explicit, and on the type of the ODE. More precisely, we have the following table. | | Nonlinear | Quasilinear | Semilinear | Linear | @@ -107,7 +111,8 @@ When the system is linear, another important practical consideration is whether * If the linear system comes from an explicit scheme, the matrix of the system is constant if the mass matrix is. This means that the ODE has to be quasilinear. * If the linear system comes from an implicit scheme, all the linear forms must be constant for the system to have a constant matrix. -## Reuse across iterations +### Reuse across iterations + For performance reasons, it is thus important that the ODE be described in the most specific way. In particular, we consider that the mass term of a quasilinear ODE is not constant, because if it is, the ODE is semilinear. We enable the user to specify the following constant annotations: * For nonlinear and quasilinear ODE, no quantity can be described as constant. * For a semilinear ODE, whether the mass term is constant. @@ -115,10 +120,12 @@ For performance reasons, it is thus important that the ODE be described in the m If a linear form is constant, regardless of whether the numerical scheme relies on a linear or nonlinear system, it is always possible to compute the jacobian of the residual with respect to the corresponding time derivative only once and retrieve it in subsequent computations of the jacobian. -# High-level API in Gridap +## High-level API in Gridap + The ODE module of `Gridap` relies on the following structure. -## Finite element spaces +### Finite element spaces + The time-dependent counterpart of `TrialFESpace` is `TransientTrialFESpace`. It is built from a standard `TestFESpace` and is equipped with time-dependent Dirichlet boundary conditions. > By definition, test spaces have zero Dirichlet boundary conditions so they need not be seen as time-dependent objects. @@ -140,7 +147,8 @@ U0 = U(t0) ∂ttU0 = ∂ttU(t0) ``` -## Cell fields +### Cell fields + The time-dependent equivalent of `CellField` is `TransientCellField`. It stores the cell field itself together with its derivatives up to the order of the ODE. For example, the following creates a `TransientCellField` with two time derivatives. @@ -151,7 +159,8 @@ u0 = zero(get_free_dof_values(U0)) u = TransientCellField(u0, (∂tu0, ∂ttu0)) ``` -## Finite element operators +### Finite element operators + The time-dependent analog of `FEOperator` is `TransientFEOperator`. It has the following constructors based on the nonlinearity type of the underlying ODE. * `TransientFEOperator(res, jacs, trial, test)` and `TransientFEOperator(res, trial, test; order)` for the version with automatic jacobians. The residual is expected to have the signature `residual(t, u, v)`. @@ -199,7 +208,8 @@ TransientLinearFEOperator((stiffness, mass), res, U, V, constant_forms=(false, t ``` If ``\kappa`` is constant, the keyword `constant_forms` could be replaced by `(true, true)`. -## The `TimeSpaceFunction` constructor +### The `TimeSpaceFunction` constructor + Apply differential operators on a function that depends on time and space is somewhat cumbersome. Let `f` be a function of time and space, and `g(t) = x -> f(t, x)` (as in the prescription of the boundary conditions `g` above). Applying the operator ``\partial_{t} - \Delta`` to `g` and evaluating at ``(t, x)`` is written `∂t(g)(t)(x) - Δ(g(t))(x)`. The constructor `TimeSpaceFunction` allows for simpler notations: let `h = TimeSpaceFunction(g)`. The object `h` is a functor that supports the notations @@ -209,7 +219,8 @@ The constructor `TimeSpaceFunction` allows for simpler notations: let `h = TimeS for all spatial and temporal differential operator, i.e. `op` in `(time_derivative, gradient, symmetric_gradient, divergence, curl, laplacian)` and their symbolic aliases (`∂t`, `∂tt`, `∇`, ...). The operator above applied to `h` and evaluated at `(t, x)` can be conveniently written `∂t(h)(t, x) - Δ(h)(t, x)`. -## Solver and solution +### Solver and solution + The next step is to choose an ODE solver (see below for a full list) and specify the boundary conditions. The solution can then be iterated over until the final time is reached. For example, to use the ``\theta``-method with a nonlinear solver, one could write @@ -234,32 +245,38 @@ for (tn, un) in enumerate(sol) end ``` -# Low-level implementation +## Low-level implementation + We now briefly describe the low-level implementation of the ODE module in `Gridap`. -## ODE operators +### ODE operators + The `ODEOperator` type represents an ODE according to the description above. It implements the `NonlinearOperator` interface, which enables the computation of residuals and jacobians. The algebraic equivalent of `TransientFEOperator` is an `ODEOpFromTFEOp`, which is a subtype of `ODEOperator`. Conceptually, `ODEOpFromTFEOp` can be thought of as an assembled `TransientFEOperator`, i.e. it deals with vectors of degrees of freedom. This operator comes with a cache (`ODEOpFromTFEOpCache`) that stores the transient space, its evaluation at the current time step, a cache for the `TransientFEOperator` itself (if any), and the constant forms (if any). > For now `TransientFEOperator` does not implement the `FEOperator` interface, i.e. it is not possible to evaluate residuals and jacobians directly on it. Rather, they are meant to be evaluated on the `ODEOpFromFEOp`. This is to cut down on the number of conversions between a `TransientCellField` and its vectors of degrees of freedom (one per time derivative). Indeed, when linear forms are constant, no conversion is needed as the jacobian matrix will be stored. -## ODE solvers +### ODE solvers + An ODE solver has to implement the following interface. * `allocate_odecache(odeslvr, odeop, t0, us0)`. This function allocates a cache that can be reused across the three functions `ode_start`, `ode_march!`, and `ode_finish!`. In particular, it is necessary to call `allocate_odeopcache` within this function, so as to instantiate the `ODEOpFromTFEOpCache` and be able to update the Dirichlet boundary conditions in the subsequent functions. * `ode_start(odeslvr, odeop, t0, us0, odecache)`. This function creates the state vectors from the initial conditions. By default, this is the identity. * `ode_march!(stateF, odeslvr, odeop, t0, state0, odecache)`. This is the update map that evolves the state vectors. * `ode_finish!(uF, odeslvr, odeop, t0, tF, stateF, odecache)`. This function converts the state vectors into the evaluation of the solution at the current time step. By default, this copies the first state vector into `uF`. -## Stage operator +### Stage operator + A `StageOperator` represents the linear or nonlinear operator that a numerical scheme relies on to evolve the state vector. It is essentially a special kind of `NonlinearOperator` but it overwrites the behaviour of nonlinear and linear solvers to take advantage of the matrix of a linear system being constant. The following subtypes of `StageOperator` are the building blocks of all numerical schemes. * `LinearStageOperator` represents the system ``\boldsymbol{J} \boldsymbol{x} + \boldsymbol{r} = \boldsymbol{0}``, and can build ``\boldsymbol{J}`` and ``\boldsymbol{r}`` by evaluating the residual at a given point. * `NonlinearStageOperator` represents ``\boldsymbol{r}(\boldsymbol{t}, \boldsymbol{\ell}_{0}(\boldsymbol{x}), \ldots, \boldsymbol{\ell}_{N}(\boldsymbol{x})) = \boldsymbol{0}``, where it is assumed that all the ``\boldsymbol{\ell}_{k}(\boldsymbol{x})`` are linear in ``\boldsymbol{x}``. -## ODE solution +### ODE solution + This type is a simple wrapper around an `ODEOperator`, an `ODESolver`, and initial conditions that can be iterated on to evolve the ODE. -# Numerical schemes formulation and implementation +## Numerical schemes formulation and implementation + We conclude this note by describing some numerical schemes and their implementation in `Gridap`. Suppose that the scheme has been evolved up to time ``t_{n}`` already and that the state vectors ``\{\boldsymbol{s}\}_{n}`` are known. We are willing to evolve the ODE up to time ``t_{n+1} > t_{n}``, i.e. compute the state vectors ``\{\boldsymbol{s}\}_{n+1}``. Generally speaking, a numerical scheme constructs an approximation of the map ``\{\boldsymbol{s}\}_{n} \to \{\boldsymbol{s}\}_{n+1}`` by solving one or more relationships of the type @@ -272,7 +289,8 @@ We now describe the numerical schemes implemented in `Gridap` using this framewo We also briefly characterise these schemes in terms of their order and linear stability. -## ``\theta``-method +### ``\theta``-method + This scheme is used to solve first-order ODEs and relies on the simple state vector ``\{\boldsymbol{s}(t)\} = \{\boldsymbol{u}(t)\}``. This means that the starting and finishing procedures are simply the identity. The ``\theta``-method relies on the following approximation @@ -312,7 +330,8 @@ By looking at the behaviour of the stability function at infinity, we find that * ``\theta = \frac{1}{2}``. The stability region is the whole left complex plane, so the scheme is ``A``-stable. This case is known as the implicit midpoint scheme. * ``\theta > \frac{1}{2}``. The stability region is the whole complex plane except the circle of radius ``\frac{1}{2 \theta - 1}`` centered at ``\left(\frac{1}{2 \theta - 1}, 0\right)``. In particular, the scheme is ``A``-stable. The special case ``\theta = 1`` is known as the Backward Euler scheme. -## Generalised-``\alpha`` scheme for first-order ODEs +### Generalised-``\alpha`` scheme for first-order ODEs + This scheme relies on the state vector ``\{\boldsymbol{s}(t)\} = \{\boldsymbol{u}(t), \partial_{t} \boldsymbol{u}(t)\}``. In particular, it needs a nontrivial starting procedure that evaluates ``\partial_{t} \boldsymbol{u}(t_{0})`` by enforcing a zero residual at ``t_{0}``. The finaliser can still return the first vector of the state vectors. For convenience, let ``\partial_{t} \boldsymbol{u}_{n}`` denote the approximation ``\partial_{t} \boldsymbol{u}(t_{n})``. > Alternatively, the initial velocity can be provided manually: when calling `solve(odeslvr, tfeop, t0, tF, uhs0)`, set `uhs0 = (u0, v0, a0)` instead of `uhs0 = (u0, v0)`. This is useful when enforcing a zero initial residual would lead to a singular system. @@ -344,6 +363,7 @@ t_{n + \alpha_{F}} &= (1 - \alpha_{F}) t_{n} + \alpha_{F} t_{n+1}, \\ The state vector is updated to ``\{\boldsymbol{s}\}_{n+1} = \{\boldsymbol{u}_{n+1}, \partial_{t} \boldsymbol{u}_{n+1}\}``. ##### Analysis + The amplification matrix for the state vector is ```math \boldsymbol{A}(z) = \frac{1}{\alpha_{M} - \alpha_{F} \gamma z} \begin{bmatrix}\alpha_{M} + (1 - \alpha_{F}) \gamma z & \alpha_{M} - \gamma \\ z & \alpha_{M} - 1 + \alpha_{F} (1 - \gamma) z\end{bmatrix}. @@ -384,7 +404,8 @@ This scheme was originally devised to control the damping of high frequencies. O ``` where ``\rho_{\infty}`` is the spectral radius at infinity. Setting ``\rho_{\infty}`` cuts all the highest frequencies in one step, whereas taking ``\rho_{\infty} = 1`` preserves high frequencies. -## Runge-Kutta +### Runge-Kutta + Runge-Kutta methods are multi-stage, i.e. they build estimates of ``\boldsymbol{u}`` at intermediate times between ``t_{n}`` and ``t_{n+1}``. They can be written as follows ```math \begin{align*} @@ -399,6 +420,7 @@ where ``p`` is the number of stages, ``\boldsymbol{A} = (a_{ij})_{1 \leq i, j \l **Implementation details** It is particularly advantageous to save the factorisation of the matrices of the stage operators for Runge-Kutta methods. This is always possible when the method is explicit and the mass matrix is constant, in which case all the stage matrices are the mass matrix. When the method is diagonally-implicit and the stiffness and mass matrices are constant, the matrices of the stage operators are ``\boldsymbol{M} + a_{ii} h_{n} \boldsymbol{K}``. In particular, if two diagonal coefficients coincide, the corresponding operators will have the same matrix. We implement these reuse strategies by storing them in `CompressedArray`s, and introducing a map `i -> NumericalSetup`. ##### Analysis + The stability function of a Runge-Kutta scheme is ```math \rho(z) = 1 + z \boldsymbol{b}^{T} (\boldsymbol{I} - z \boldsymbol{A})^{-1} \boldsymbol{1}. @@ -445,7 +467,8 @@ The analysis of Runge-Kutta methods is well-established but we only derive order \end{array}. ``` -## Implicit-Explicit Runge-Kutta +### Implicit-Explicit Runge-Kutta + When the residual has an implicit-explicit decomposition, usually because we can identify a stiff part that we want to solve implicitly and a nonstiff part that we want to solve explicitly, the Runge-Kutta method reads as follows ```math \begin{align*} @@ -475,7 +498,8 @@ Many methods can be created by padding a DIRK tableau with zeros to give it an a ``` We note that the first column of the matrix and the first weight are all zero, so the first stage for the implicit part does not need to be solved. -## Generalised-``\alpha`` scheme for second-order ODEs +### Generalised-``\alpha`` scheme for second-order ODEs + This scheme relies on the state vector ``\{\boldsymbol{s}(t)\} = \{\boldsymbol{u}(t), \partial_{t} \boldsymbol{u}(t), \partial_{tt} \boldsymbol{u}(t)\}``. It needs a nontrivial starting procedure that evaluates ``\partial_{tt} \boldsymbol{u}(t_{0})`` by enforcing a zero residual at ``t_{0}``. The finaliser can still return the first vector of the state vectors. For convenience, let ``\partial_{tt} \boldsymbol{u}_{n}`` denote the approximation ``\partial_{tt} \boldsymbol{u}(t_{n})``. > The initial acceleration can alternatively be provided manually: when calling `solve(odeslvr, tfeop, t0, tF, uhs0)`, set `uhs0 = (u0, v0, a0)` instead of `uhs0 = (u0, v0)`. This is useful when enforcing a zero initial residual would lead to a singular system. @@ -496,6 +520,7 @@ t_{n + 1 - \alpha_{F}} &= \alpha_{F} t_{n} + (1 - \alpha_{F}) t_{n+1}, \\ The state vector is then updated to ``\{\boldsymbol{s}\}_{n+1} = \{\boldsymbol{u}_{n+1}, \partial_{t} \boldsymbol{u}_{n+1}, \partial_{tt} \boldsymbol{u}_{n+1}\}``. ##### Analysis + The amplification matrix for the state vector is ```math \boldsymbol{A}(z) = \frac{1}{\overline{\alpha_{M}} + \overline{\alpha_{F}} \beta z^{2}} \begin{bmatrix} @@ -526,7 +551,7 @@ This method was also designed to damp high-frequency perturbations so it is comm * The standard generalised-``\alpha`` method is obtained by setting ``\alpha_{M} = \frac{2 \rho_{\infty - 1}}{\rho_{\infty} + 1}``, ``\alpha_{F} = \frac{\rho_{\infty}}{\rho_{\infty} + 1}``. * The Newmark method corresponds to ``\alpha_{F} = \alpha_{M} = 0``. In this case, the values of ``\beta`` and ``\gamma`` are usually chosen as ``\beta = 0``, ``\gamma = \frac{1}{2}`` (explicit central difference scheme), or ``\beta = \frac{1}{4}`` and ``\gamma = \frac{1}{2}`` (midpoint rule). -# Reference +## Reference ```@autodocs Modules = [ODEs,] diff --git a/docs/src/modules/Polynomials.md b/docs/src/modules/Polynomials.md new file mode 100644 index 000000000..e092ab6bc --- /dev/null +++ b/docs/src/modules/Polynomials.md @@ -0,0 +1,339 @@ +# Gridap.Polynomials + +```@meta +CurrentModule = Gridap.Polynomials +``` + +The module documentation is organised as follows: +- Summary of the main features +- Mathematical definitions of the families of [polynomial bases](@ref "Families + of polynomial bases") and spanned polynomial [spaces](@ref "Polynomial spaces + in FEM") +- Docstrings of implemented [families](@ref "Types for polynomial families") + and [bases](@ref "Polynomial bases") of polynomials +- [Low level](@ref "Low level APIs and internals") APIs, internals and + [deprecated](@ref "Deprecated APIs") methods + +### Summary + +```@docs +Polynomials +``` + +## Mathematical definitions + +### Families of polynomial bases + +#### Monomials + +The [`Monomial`](@ref)s are the standard basis polynomials of general +polynomial spaces. The order ``K`` 1D monomial is +```math +x \rightarrow x^K, +``` +and the order ``\bm{K}=(K_1, K_2, \dots, K_D)`` D-dimensional monomial is defined by +```math +\bm{x} = (x_1, x_2, \dots, x_D) \longrightarrow +\bm{x}^{\bm{K}} = x_1^{K_1}x_2^{K_2}...x_D^{K_D} = \Pi_{i=1}^D +x_i^{K_i}. +``` + +#### Legendre polynomials + +The Legendre polynomials are the orthogonal 1D basis recursively defined by +```math +P_0(x) = 1,\qquad +P_1(x) = x,\qquad +P_{n+1}(x) = \frac{1}{(n+1)}( (2n+1)x P_{n}(x)-n P_{n-1}(x) ), +``` +the orthogonality is for the ``L^2`` scalar product on ``[-1,1]``. + +This module implements the normalized shifted [`Legendre`](@ref) polynomials, +shifted to be orthogonal on ``[0,1]`` using the change of variable ``x +\rightarrow 2x-1``, leading to +```math +P^*_{n}(x)=\frac{1}{\sqrt{2n+1}}P_n(2x-1)=\frac{1}{\sqrt{2n+1}}(-1)^{n}∑ _{i=0}^{n}{\binom{n}{i}}{\binom{n+i}{i}}(-x)^{i}. +``` + +#### Chebyshev polynomials + +The first kind Chebyshev polynomials ``T_n`` and second kind Chebyshev +polynomials ``U_n`` can be recursively defined by +```math + T_{0}(x)=1,\qquad T_{1}(x)=\ \,x,\qquad T_{n+1}(x)=2x\,T_{n}(x)-T_{n-1}(x).\\ + U_{0}(x)=1,\qquad U_{1}(x)=2x,\qquad U_{n+1}(x)=2x\,U_{n}(x)-U_{n-1}(x). +``` +or explicitly defined by +```math +T_{n}(x)=∑ _{i=0}^{\left\lfloor {n}/{2}\right\rfloor }{\binom + {n}{2i}}\left(x^{2}-1\right)^{i}x^{n-2i},\qquad +U_{n}(x)=∑ _{i=0}^{\left\lfloor {n}/{2}\right\rfloor }{\binom + {n+1}{2i+1}}\left(x^{2}-1\right)^{i}x^{n-2i}, +``` +where ``\left\lfloor {n}/2\right\rfloor`` is `floor(n/2)`. + +Similarly to Legendre above, this module implements the shifted first kind +Chebyshev by [`Chebyshev{:T}`](@ref), defined by +```math +T^*_n(x) = T_n(2x-1). +``` +The analog second kind shifted Chebyshev polynomials can be implemented by +[`Chebyshev{:U}`](@ref) in the future. + +#### Bernstein polynomials + +The univariate [`Bernstein`](@ref) polynomials forming a basis of ``ℙ_K`` +are defined by +```math +B^K_{n}(x) = \binom{K}{n} x^n (1-x)^{K-n}\qquad\text{ for } 0 ≤ n ≤ K. +``` + +The ``D``-multivariate Bernstein polynomials of degree ``K`` are defined by +```math +B^{D,K}_α(\bm{x}) = \binom{K}{α} λ(\bm{x})^α\qquad\text{for all }α ∈\mathcal{I}_K^D +``` +where +- ``\mathcal{I}^D_K=\{ α ∈ ℤ_+^{N} \quad\big|\quad |α| = K \}`` where ``N = D+1``, +- ``\binom{|α|}{α} = \frac{|α|!}{α!} = \frac{K!}{α_1 !α_2 !\dotsα_N!}`` and ``K=|α|=∑_{1 ≤ i ≤ N} α_i``, +and ``λ(\bm x)`` is the set of barycentric coordinates of ``\bm x`` relative to a given simplex, +see the developer notes on the [Bernstein bases algorithms](@ref). + +The superscript ``D`` and ``K`` in ``B^{D,K}_α(x)`` can be omitted because they +are always determined by ``α`` using ``{D=\#(α)-1}`` and ``K=|α|``. The set +``\{B_α\}_{α∈\mathcal{I}_K^D}`` is a basis of ``ℙ^D_K``, implemented by +[`BernsteinBasisOnSimplex`](@ref). + +The Bernstein polynomials sum to ``1``, and are positive in the simplex. + +#### ModalC0 polynomials + +The [`ModalC0`](@ref) polynomials are 1D hierarchical and orthogonal +polynomials ``\phi_K`` for which ``\phi_K(0) = \delta_{0K}`` and ``\phi_K(1) = +\delta_{1K}``. This module implements the generalised version introduced in Eq. +17 of [Badia-Neiva-Verdugo 2022](https://doi.org/10.1016/j.camwa.2022.09.027). + +When `ModalC0` is used as a `<:Polynomial` parameter in +[`CartProdPolyBasis`](@ref) or other bases (except `ModalC0Basis`), the trivial +bounding box `(a=Point{D}(0...), b=Point{D}(1...))` is assumed, which +coincides with the usual definition of the ModalC0 bases. + + +### Polynomial spaces in FEM + +#### P and Q spaces + +Let us denote ``ℙ_K(x)`` the space of univariate polynomials of order up to ``K`` in the varible ``x`` +```math +ℙ_K(x) = \text{Span}\big\{\quad x\rightarrow x^i \quad\big|\quad 0 ≤ i ≤ K \quad\big\}. +``` + +Then, ``ℚ^D`` and ``ℙ^D`` are the spaces for Lagrange elements +on D-cubes and D-simplices respectively, defined by +```math +ℚ^D_K = \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^α \quad\big|\quad 0 ≤ + α_1, α_2, \dots, α_D ≤ K \quad\big\}, +``` +and +```math +ℙ^D_K = \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^α \quad\big|\quad 0 ≤ + α_1, α_2, \dots, α_D ≤ K;\quad ∑_{d=1}^D α_d ≤ + K \quad\big\}. +``` + +To note, there is ``ℙ_K = ℙ^1_K = ℚ^1_K``. + +#### Serendipity space Sr + +The serendipity space, commonly used for serendipity finite elements on n-cubes, +are defined by +```math +𝕊r^D_K = \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^α \quad\big|\quad 0 ≤ + α_1, α_2, \dots, α_D ≤ K;\quad + ∑_{d=1}^D α_d\;\mathbb{1}_{[2,K]}(α_d) ≤ K \quad\big\} +``` +where ``\mathbb{1}_{[2,K]}(α_d)`` is ``1`` if ``α_d ≥ 2`` or else +``0``. + +#### Homogeneous P and Q spaces + +It will later be useful to define the homogeneous Q spaces +```math +\tilde{ℚ}^D_K = ℚ^D_K\backslashℚ^D_{K-1} = + \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^α \quad\big|\quad 0 ≤ α_1, + α_2, \dots, α_D ≤ K; \quad \text{max}(α) = K \quad\big\}, +``` +and homogeneous P spaces +```math +\tilde{ℙ}^D_K = ℙ^D_K\backslash ℙ^D_{K-1} = + \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^α \quad\big|\quad 0 ≤ α_1, + α_2, \dots, α_D ≤ K;\quad ∑_{d=1}^D α_d = K \quad\big\}. +``` + + +#### Nédélec ``curl``-conforming spaces + +The Kᵗʰ Nédélec polynomial spaces on respectively rectangles and +triangles are defined by +```math +ℕ𝔻^2_K(\square) = \left(ℚ^2_K\right)^2 ⊕ + \left(\begin{array}{c} y^{K+1}\,ℙ_K(x)\\ x^{K+1}\,ℙ_K(y) \end{array}\right) +,\qquad +ℕ𝔻^2_K(\bigtriangleup) =\left(ℙ^2_K\right)^2 ⊕\bm{x}\times(\tilde{ℙ}^2_K)^2, +``` +where ``\times`` here means ``\left(\begin{array}{c} x\\ y +\end{array}\right)\times\left(\begin{array}{c} p(\bm{x})\\ q(\bm{x}) +\end{array}\right) = \left(\begin{array}{c} y p(\bm{x})\\ -x q(\bm{x}) +\end{array}\right)`` and ``⊕`` is the direct sum of vector spaces. + +Then, the Kᵗʰ Nédélec polynomial spaces on respectively hexahedra and +tetrahedra are defined by +```math +ℕ𝔻^3_K(\square) = \left(ℚ^3_K\right)^3 ⊕ \bm{x}\times(\tilde{ℚ}^3_K)^3,\qquad +ℕ𝔻^3_K(\bigtriangleup) =\left(ℙ^3_K\right)^3 ⊕ \bm{x}\times(\tilde{ℙ}^3_K)^3. +``` + +``ℕ𝔻^D_K(\square)`` and ``ℕ𝔻^D_K(\bigtriangleup)`` are of +order K+1 and the curl of their elements are in ``(ℚ^D_K)^D`` +and ``(ℙ^D_K)^D`` respectively. + +#### Raviart-Thomas and Nédélec ``div``-conforming Spaces + +The Kᵗʰ Raviart-Thomas polynomial spaces on respectively D-cubes and +D-simplices are defined by +```math +ℕ𝔻^D_K(\square) = \left(ℚ^D_K\right)^D ⊕ \bm{x}\;\tilde{ℚ}^D_K, \qquad +ℕ𝔻^D_K(\bigtriangleup) = \left(ℙ^D_K\right)^D ⊕ \bm{x}\;\tilde{ℙ}^D_K, +``` +these bases are of dimension K+1 and the divergence of their elements are in +``ℚ^D_K`` and ``ℙ^D_K`` respectively. + +#### ``ℙ_r^{-}Λ^k`` and ``ℙ_rΛ^k`` bases + +Those bases are a generalization of the scalar Bernstein bases to the spaces for +the two principal finite element families forming a de Rham complex on simplices. +They are respectively implemented by [`BarycentricPmΛBasis`](@ref) and +[`BarycentricPmΛBasis`](@ref). Their definition with references, and implementation +details are provided in [this](@ref "Bernstein-basis-generalization-for-ℙΛ-spaces") +developer note. + + +#### Filter functions + +Some `filter` functions are used to select which terms of a `D`-dimensional +tensor product space of 1D polynomial bases are to be used to create a +`D`-multivariate basis. When a filter can be chosen, the default filter is +always the trivial filter for space of type ℚ, yielding the full tensor-product +space. + +The signature of the filter functions should be + + (term,order) -> Bool + +where `term` is a tuple of `D` integers containing the exponents of a +multivariate monomial, that correspond to the multi-index ``α`` previously +used in the P/Q spaces definitions. + +The following example filters can be used to define associated polynomial spaces: + +| space | filter | possible family | +| :-----| :------------------------------------------------------------| :------------------------------------ | +| ℚᴰ | `_q_filter(e,order) = maximum(e) <= order` | All | +| ℚ̃ᴰₙ | `_qh_filter(e,order) = maximum(e) == order` | [`Monomial`](@ref) | +| ℙᴰ | `_p_filter(e,order) = sum(e) <= order` | [`hierarchical`](@ref isHierarchical) | +| ℙ̃ᴰₙ | `_ph_filter(e,order) = sum(e) == order` | [`Monomial`](@ref) | +| 𝕊rᴰₙ | `_ser_filter(e,order) = sum( i for i in e if i>1 ) <= order` | [`hierarchical`](@ref isHierarchical) | + +## Types for polynomial families + +The following types represent particular polynomial bases 'families' or 'types', later +shortened as `PT` in type parameters. + +```@docs +Polynomial +``` +![](../assets/poly_1.svg) +!!! warning + [`Polynomial`](@ref)s do not implement the `Field` interface, only the + [`PolynomialBasis`](@ref) can be evaluated. + + +```@docs +isHierarchical +Monomial +Legendre +Chebyshev +``` +!!! todo + Second kind `Chebyshev{:U}` are not implemented yet. + +```@docs +Bernstein +ModalC0 +``` + +## Polynomial bases + +```@docs +PolynomialBasis +get_order(::PolynomialBasis) +get_orders(::PolynomialBasis) +get_dimension(::PolynomialBasis) +FEEC_poly_basis +FEEC_space_definition_checks +MonomialBasis(args...) +MonomialBasis +LegendreBasis(args...) +LegendreBasis +ChebyshevBasis(args...) +ChebyshevBasis +BernsteinBasis(args...) +``` +!!! warning + Calling `BernsteinBasis` with the filters (e.g. a `_p_filter`) rarely + yields a basis for the associated space (e.g. ``ℙ``). Indeed, the + term numbers do not correspond to the degree of the polynomial, because the + basis is not [`hierarchical`](@ref isHierarchical). + +```@docs +BernsteinBasis +BernsteinBasisOnSimplex +BernsteinBasisOnSimplex(::Val,::Type,::Int,vertices=nothing) +bernstein_terms +bernstein_term_id +BarycentricPmΛBasis(::Val{D},::Type{T},r,k,vertices=nothing; kwargs...) where {D,T} +BarycentricPΛBasis(::Val{D},::Type{T},r,k,vertices=nothing; kwargs...) where {D,T} +``` +## Low level APIs and internals + +![](../assets/poly_2.svg) + +```@docs +CartProdPolyBasis +CartProdPolyBasis(::Type, ::Val{D}, ::Type, ::Int, ::Function) where D +CartProdPolyBasis(::Type, ::Val{D}, ::Type{V}, ::NTuple{D,Int}, ::Function) where {D,V} +get_exponents +CompWiseTensorPolyBasis +NedelecPolyBasisOnSimplex +RaviartThomasPolyBasis +ModalC0Basis +ModalC0Basis() +BarycentricPmΛBasis +BarycentricPΛBasis +BarycentricPmΛBasis(::BarycentricPmΛBasis{D,V,LN,B}, ::Vector{Int}...) where {D,V,LN,B} +BarycentricPΛBasis(::BarycentricPΛBasis{D,V,LN,B}, ::Vector{Int}...) where {D,V,LN,B} +print_indices +get_bubbles +PΛ_bubbles +PmΛ_bubbles +BarycentricPΛIndices +``` + +### Deprecated APIs + +```@docs +num_terms +PCurlGradMonomialBasis +QGradMonomialBasis +QCurlGradMonomialBasis +JacobiPolynomialBasis +``` diff --git a/docs/src/modules/ReferenceFEs.md b/docs/src/modules/ReferenceFEs.md new file mode 100644 index 000000000..ae1eb31af --- /dev/null +++ b/docs/src/modules/ReferenceFEs.md @@ -0,0 +1,282 @@ +```@meta +CurrentModule = Gridap.ReferenceFEs +``` + +# Gridap.ReferenceFEs + +#### Contents + +```@contents +Pages = ["ReferenceFEs.md"] +Depth = 2:3 +``` + +## Reference FE summary + +A reference finite element is defined using the following signature +```@docs; canonical=false +ReferenceFE(::ReferenceFEName, args...; kwargs...) +``` + +The following table summarizes the elements implemented in Gridap (legend below). + +| Name | Gridap name | FEEC name | Polytopes | Order | Conformity| +| :-------------------------------------------------------------------------------------- | :------------------------------------------- | :-------- | :---------- | --------------: | :-------- | +| [Lagrangian](https://defelement.org/elements/lagrange.html) | [`lagrangian`](@ref LagrangianRefFE) | 𝓟ᵣ⁽⁻⁾Λ⁰ | △ | ``{r=o≥1, o}`` | `:H1` | +| | | 𝓠ᵣ⁻Λ⁰ | ``\square`` | ``{r=o≥1, o}`` | `:H1` | +| | | |`WEDGE`, `PYRAMID` | ``{o≥1, o}`` | `:H1` | +| [Serendipity](https://defelement.org/elements/serendipity.html) | [`serendipity`](@ref SerendipityRefFE) | 𝓢ᵣΛ⁰ | ``\square`` | ``{r=o≥1, o}`` | `:H1` | +| [Bezier](https://defelement.org/elements/bernstein.html) | [`bezier`](@ref BezierRefFE) | 𝓟ᵣ⁻Λ⁰ | △ | ``{r=o≥1, o}`` | `:H1` | +| | | 𝓠ᵣ⁻Λ⁰ | ``\square`` | ``{r=o≥1, o}`` | `:H1` | +| [ModalC0](https://doi.org/10.48550/arXiv.2201.06632) | [`modalC0`](@ref ModalC0RefFE) | 𝓠ᵣ⁻Λ⁰ | ``\square`` | ``{r=o≥1, o}`` | `:H1` | +| | +| [Modal scalar](@ref "Modal and nodal scalar reference elements") | [`modal_lagrangian`](@ref ModalScalarRefFE) | 𝓟/𝓠ᵣ⁻Λ⁰ |△,``\square``| ``{r=o≥1, o}`` | `:H1` | +| | [`modal_serendipity`](@ref ModalScalarRefFE) | SᵣΛ⁰ | ``\square`` | ``{r=o≥1, o}`` | `:H1` | +| | +| [Nédélec (first kind)](https://defelement.org/elements/nedelec1.html) | [`nedelec`](@ref NedelecRefFE) | 𝓟ᵣ⁻Λ¹ | `TRI`,`TET` | ``{r=o+1≥1, r}``| `:Hcurl` | +| | | 𝓠ᵣ⁻Λ¹ | `QUAD`,`HEX`| ``{r=o+1≥1, r}``| `:Hcurl` | +| [Nédélec (second kind)](https://defelement.org/elements/nedelec2.html) | [`nedelec2`](@ref NedelecRefFE) | 𝓟ᵣΛ¹ | `TRI`,`TET` | ``{r=o≥1, r}``| `:Hcurl` | +| | +| [Raviart-Thomas](https://defelement.org/elements/raviart-thomas.html) | [`raviart_thomas`](@ref LagrangianRefFE) | 𝓟ᵣ⁻Λᴰ⁻¹ | `TRI`,`TET` | ``{r=o+1≥1, r}``| `:Hdiv` | +| | | 𝓠ᵣ⁻Λᴰ⁻¹ | `QUAD`,`HEX`| ``{r=o+1≥1, r}``| `:Hdiv` | +| [Brezzi-Douglas-Marini](https://defelement.org/elements/brezzi-douglas-marini.html) | [`bdm`](@ref BDMRefFE) | 𝓟ᵣΛᴰ⁻¹ | `TRI`,`TET` | ``{r=o≥1, r}``| `:Hdiv` | +| [Mardal-Tai-Winther](https://defelement.org/elements/mardal-tai-winther.html) | `TODO` `mtw` | | `TRI`,`TET` | ``{o=1, D+1}`` | `:Hdiv` | +| | +| [Crouzeix-Raviart](https://defelement.org/elements/crouzeix-raviart.html) |[`crouzeix_raviart`](@ref CrouzeixRaviartRefFE)| | `TRI` | ``{o=1, o}`` | `:L2` | +| [discontinuous Lagrangian](https://defelement.org/elements/discontinuous-lagrange.html) | [`lagrangian`](@ref LagrangianRefFE) | 𝓟ᵣ⁻Λᴰ | △ | ``{r-1=o≥0, o}``| `:L2` | +| | | 𝓟ᵣΛᴰ | △ | ``{r=o≥0, o}`` | `:L2` | +| | | 𝓠ᵣ⁻Λᴰ | ``\square`` | ``{r-1=o≥0, o}``| `:L2` | +| | kwarg `space=:P` | 𝓢ᵣΛᴰ | ``\square`` | ``{r=o≥0, o}`` | `:L2` | +| [MINI bubble](@ref "Bubble reference element") | [`bubble`](@ref BubbleRefFE) | |△,``\square``| ``{o=1, 2}`` | `:L2` | +| Bezier, ModalC0 | as above | | | ``{o≥0, o}`` | `:L2` | +| | +| [Arnold-Winther](https://defelement.org/elements/arnold-winther.html) | `TODO` `arnoldwinther` | | `TRI` | ``{o=2, 4}`` | `:Hdiv` | +| [Hellan-Herrmann-Jhonson](https://defelement.org/elements/hellan-herrmann-johnson.html) | `TODO` `hhj` | | `TRI` | ``{TODO, o}`` | `:Hdiv` | + +##### Legend + +- Name: usual name of the element and link to its + [DefElement](https://defelement.org/) page, containing all the details + defining the element and references. +- Gridap name: the name to use in the Gridap APIs (it is a [`ReferenceFEName`](@ref ReferenceFEs.ReferenceFEName) + singleton), with a link to the docstring of the element constructor. +- FEEC name: name of the element family in the [Periodic Table of the Finite + Elements](https://www-users.cse.umn.edu/~arnold/femtable/index.html) [1] +- Polytopes: + - △ simplices (`SEGMENT`, `TRI` (triangle), `TET` (tetrahedron)) + - ``\square`` n-cubes (`SEGMENT`, `QUAD` (quadridateral), `HEX` (hexahedron)) +- Order: ( _definition of ``r`` and ``o``_; _``k``_ ) where + - ``r`` is the FEEC polynomial degree parameter (if defined). + - ``o`` is the `order` parameter of the non FEEC `ReferenceFE` constructor (using [`name::ReferenceFEName`](@ref ReferenceFEName)), + - ``k`` is the maximum polynomial order of the shape functions in one direction (Lagrange superdegree), defined in function of ``r`` or ``o``, +- Conformity: supported [`Conformity`](@ref). All the elements also implement `:L2` conformity. + +#### Additional information + +###### Anisotropic and Cartesian product elements + +The `lagrangian`, `modalC0` and `bezier` elements support anisotropic orders on +`QUAD` and `HEX`, leveraging the tensor product basis in each dimension. + +Also, a Cartesian product finite-element space is available for all the `:H1` +conforming elements, for all polytopes. It means that a tensor type +([`<:MultiValue`](@ref Gridap.TensorValues)) can be given as value type +argument `T`, for example `VectorValue{3,Float64}` or +`SymTensorValue{2,Float64}`. The DoFs are duplicated for each independent +component of the tensor. + +The `modalC0` element has the particularity that the support of its +shape-function can be scaled to be adapted to the physical element. + +###### `poly_type` keyword argument + +The `lagrangian`, `nedelec`, `raviart_thomas`, `bdm`, `serendipity`, and +`modal_scalar` element constructors support the `poly_type::Type{<:Polynomial}` +keyword argument, which gives the choice of the polynomial family to use as +(pre-)basis for the approximation space of the element. This changes the +*basis* but not the spanned polynomial *space* (for affine-mapped reference +elements). + +`poly_type` defaults to `Bernstein` on simplices and to `ModalC0` on n-cubes +(incl. `SEGMENT`), except for `lagrangian` and `serendipity` elements for which +it defaults to `Monomial`. + +###### `mom_poly_type` keyword argument + +Additionally, the `modal_scalar`, `nedelec`, `raviart_thomas`, `bdm` element +constructors support the `mom_poly_type::Type{<:Polynomial}` keyword argument, +which gives the choice of the polynomial basis to use as "test" polynomials in +the moment functionals defining the DoFs (``q`` in [1, eq. (2)]). + +`mom_poly_type` defaults to `poly_type`. + +###### `change_dof` keyword argument + +The `change_dof::Bool` argument is available for reference FEs using moment based +DoFs, to tell the constructor to use the polynomial basis directly as +shape-functions basis, and to define the DoFs as a change of basis from the +usual DoF (treating them as "pre-"DoFs). It is available, and defaults to +`true`, for `modal_scalar`, `nedelec`, `raviart_thomas` and `bdm` elements. See +the [Geometric decompositions](@ref "Geometric decompositions") section for +more detail. + +The `mom_poly_type` and `poly_type` keywords change the choice of DoF *basis* +when `change_dof` is `false` and `true` respectively, but not the DoF *space* +(polynomial space dual). + +##### Modal and nodal scalar reference elements + +`lagrangian`, `modalC0`, `bezier` and `serendipity` are nodal elements. +`lagrangian` and `serendipity` can be constructed via the constructor using +FEEC notations by passing the keyword argument `nodal=true`. + +By default, `nodal` is `false`, so these FEEC constructors return +`modal_scalar` ([`ModalScalar{F}()`](@ref ModalScalarRefFE)) elements whose DOFs +are the FEEC moments described in [1]. + +The `ModalScalar` elements support all `poly_type`, `mom_poly_type` and `change_dof` +keyword arguments, while the nodal ones only support `poly_type`. + +###### Bubble reference element + +The [`BubbleRefFE`](@ref) currently implements bubble space for MINI element, +but the bubble can be fine-tuned. The MINI element on `TRI`angle is documented +[here](https://defelement.org/elements/mini.html). + +```@docs +ReferenceFEs +``` + +## Polytopes + +### Abstract API + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["/Polytopes.jl"] +``` + +### Extrusion Polytopes + +| Name | Extrusion | Nodes | Edges | Faces | +| :--- | :-------- | :---- | :---- | :---- | +| `SEGMENT` | `(HEX,)` | ![](../assets/polytopes/SEGMENT_0.svg) | | | +| `TRI` | `(TET,TET)` | ![](../assets/polytopes/TRI_0.svg) | ![](../assets/polytopes/TRI_1.svg) | | +| `QUAD` | `(HEX,HEX)` | ![](../assets/polytopes/QUAD_0.svg) | ![](../assets/polytopes/QUAD_1.svg) | | +| `HEX` | `(HEX,HEX,HEX)` | ![](../assets/polytopes/HEX_0.svg) | ![](../assets/polytopes/HEX_1.svg) | ![](../assets/polytopes/HEX_2.svg) | +| `TET` | `(TET,TET,TET)` | ![](../assets/polytopes/TET_0.svg) | ![](../assets/polytopes/TET_1.svg) | ![](../assets/polytopes/TET_2.svg) | +| `WEDGE` | `(TET,TET,HEX)` | ![](../assets/polytopes/WEDGE_0.svg) | ![](../assets/polytopes/WEDGE_1.svg) | ![](../assets/polytopes/WEDGE_2.svg) | +| `PYRAMID` | `(HEX,HEX,TET)` | ![](../assets/polytopes/PYRAMID_0.svg) | ![](../assets/polytopes/PYRAMID_1.svg) | ![](../assets/polytopes/PYRAMID_2.svg) | + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["ExtrusionPolytopes.jl"] +``` + +### General Polytopes + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["GeneralPolytopes.jl"] +``` + +## Quadratures + +### Abstract API + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["/Quadratures.jl"] +``` + +### Available Quadratures + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["TensorProductQuadratures.jl","DuffyQuadratures.jl","StrangQuadratures.jl","XiaoGimbutasQuadratures.jl"] +``` + +## ReferenceFEs + +### Abstract API + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["ReferenceFEInterfaces.jl","Dofs.jl","LinearCombinationDofVectors.jl","Pullbacks.jl"] +``` + +### Nodal ReferenceFEs + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["LagrangianRefFEs.jl","LagrangianDofBases.jl","SerendipityRefFEs.jl","BezierRefFEs.jl","ModalC0RefFEs.jl","BubbleRefFEs.jl"] +``` + +### Moment-Based ReferenceFEs + +#### Framework + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["MomentBasedReferenceFEs.jl"] +``` + +##### Geometric decompositions + +The geometric decomposition API consist in the methods +- [`has_geometric_decomposition(polybasis,p,conf)`](@ref), +- [`get_face_own_funs(polybasis,p,conf)`](@ref), +- [`get_facet_flux_sign_flip(polybasis,p,conf)`](@ref). +where `polybasis` is a polynomial basis, `p` a polytope and `conf` a conformity. + +This API ensures that: +- each polynomial ``𝑝_i`` of the basis is associated to a face ``f`` of `p`, +- the `conf`-trace of ``𝑝_i`` (scalar trace, tangential trace, + normal trace) over another face ``g`` of `p` is zero whenever ``g`` does not + contain ``f``, and +- the polynomials owned by boundary faces can be glued together with conformity + `conf` in the physical space. + +The bases that currently support the geometric decomposition are: +- those of ``P^-Λ^k`` and ``PΛ^k`` spaces for `Bernstein` polynomial type (on simplices), see also Bernstein basis [Geometric decomposition](@ref "Geometric decomposition"), +- those of ``Q^-Λ^k`` spaces for `ModalC0` and `Bernstein` polynomial types (on n-cubes), +- those of ``SΛ^0`` spaces for `ModalC0` polynomial types (on n-cubes). + +The keyword argument `change_dof=true` means that, if possible, the shape +functions are defined as the basis polynomials of the pre-basis. This is +possible if the pre-basis verifies a geometric decomposition. Setting +`change_dof=false` forces the shape functions to be defined as the dual basis of +the DoF basis. This kwarg do not alter the polynomial space and dual space +respectively spanned by the shape-functions and the DoFs basis, but does change +the DoF basis choice for the dual space. + +The kwarg `change_dof` is available for Lagrangian, BDM, Raviart-Thomas, Nédélec +and Serendipity elements. It defaults to true except for Lagrangian and +Serendipity. `change_dof` is ignored if the pre-basis for the given `poly_type <: +Polynomial` does not admit the geometric decomposition. + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :macro, :function, :constant] +Pages = ["GeometricDecompositions.jl"] +``` + +#### Available Moment-Based ReferenceFEs + +```@autodocs +Modules = [ReferenceFEs,] +Order = [:type, :constant, :macro, :function] +Pages = ["RaviartThomasRefFEs.jl","NedelecRefFEs.jl","BDMRefFEs.jl","CrouzeixRaviartRefFEs.jl","ModalScalarRefFEs.jl"] +``` + +## References + +[1] [D.N. Arnold and A. Logg, Periodic Table of the Finite Elements, SIAM News, vol. 47 no. 9, November 2014.](https://www-users.cse.umn.edu/~arnold/papers/periodic-table.pdf) diff --git a/docs/src/TensorValues.md b/docs/src/modules/TensorValues.md similarity index 76% rename from docs/src/TensorValues.md rename to docs/src/modules/TensorValues.md index 0fe7f5265..e52b71710 100644 --- a/docs/src/TensorValues.md +++ b/docs/src/modules/TensorValues.md @@ -10,31 +10,20 @@ tensors: - 1st order [`VectorValue`](@ref), - 2nd order [`TensorValue`](@ref), - 2nd order and symmetric [`SymTensorValue`](@ref), +- 2nd order and skew symmetric [`SkewSymTensorValue`](@ref), - 2nd order, symmetric and traceless [`SymTracelessTensorValue`](@ref), - 3rd order [`ThirdOrderTensorValue`](@ref), - 4th order and symmetric [`SymFourthOrderTensorValue`](@ref). -## Generalities +## Summary -The main feature of this module is that the provided types do not extend from `AbstractArray`, but from `Number`! - -This allows one to work with them as if they were scalar values in broadcasted operations on arrays of `VectorValue` objects (also for `TensorValue` or `MultiValue` objects). For instance, one can perform the following manipulations: -```julia -# Assign a VectorValue to all the entries of an Array of VectorValues -A = zeros(VectorValue{2,Int}, (4,5)) -v = VectorValue(12,31) -A .= v # This is possible since VectorValue <: Number - -# Broadcasting of tensor operations in arrays of TensorValues -t = TensorValue(13,41,53,17) # creates a 2x2 TensorValue -g = TensorValue(32,41,3,14) # creates another 2x2 TensorValue -B = fill(t,(1,5)) -C = inner.(g,B) # inner product of g against all TensorValues in the array B -@show C -# C = [2494 2494 2494 2494 2494] +```@docs +TensorValues ``` -To create a [`::MultiValue`](@ref) tensor from components, these should be given +## Construction and conversion + +To create a [`MultiValue`](@ref) tensor from components, these should be given as separate arguments or all gathered in a `tuple`. The order of the arguments is the order of the linearized Cartesian indices of the corresponding array (order of the `Base.LinearIndices` indices): @@ -48,10 +37,9 @@ ts= convert(SMatrix{2,2,Int}, t) # 2 4 t2[1,2] == t[1,2] == 3 # true ``` -For symmetric tensor types, only the independent components should be given, see -[`SymTensorValue`](@ref), [`SymTracelessTensorValue`](@ref) and [`SymFourthOrderTensorValue`](@ref). - -## Construction and conversion +For tensor types with symmetry, only the independent components should be given, +see [`SymTensorValue`](@ref), [`SkewSymTensorValue`](@ref), +[`SymTracelessTensorValue`](@ref) and [`SymFourthOrderTensorValue`](@ref). A `MultiValue` can be created from an `AbstractArray` of the same size. If the `MultiValue` type has internal constraints (e.g. symmetries), ONLY the required @@ -71,6 +59,7 @@ s2 === s3 === s4 # true `MArray`s only. They can also be converted to julia `Array` using the constructor (similarly to StaticArrays). + ```julia v = VectorValue(1,2,3) sv1 = SVector(v) @@ -102,6 +91,7 @@ The following concrete tensor types are currently implemented: VectorValue TensorValue SymTensorValue +SkewSymTensorValue SymTracelessTensorValue ThirdOrderTensorValue SymFourthOrderTensorValue @@ -120,7 +110,7 @@ AbstractSymTensorValue getindex(::MultiValue, ::Integer) ``` -## Interface +## Interface and operations The tensor types implement methods for the following `Base` functions: `length`, `size`, `rand`, `zero`, `real`, `imag` and `conj`. @@ -138,13 +128,15 @@ mutable Mutable num_indep_components indep_comp_getindex +component_basis +representatives_of_componentbasis_dual indep_components_names change_eltype inner -dot +dot(::MultiValue,::MultiValue) double_contraction -outer +outer(::MultiValue,::MultiValue) ``` ### Other type specific interfaces @@ -154,7 +146,10 @@ outer ```@docs det inv +eigen symmetric_part +skew_symmetric_part +congruent_prod ``` #### For first order tensors @@ -179,7 +174,7 @@ meas #### For `VectorValue` of length 2 and 3 ```@docs -cross +cross(::VectorValue,::VectorValue) ``` #### For second order non-traceless and symmetric fourth order tensors @@ -188,3 +183,9 @@ cross one ``` +##### Deprecated + +```@docs +data_index +n_components +``` diff --git a/docs/src/Visualization.md b/docs/src/modules/Visualization.md similarity index 100% rename from docs/src/Visualization.md rename to docs/src/modules/Visualization.md diff --git a/docs/src/overview.md b/docs/src/overview.md new file mode 100644 index 000000000..7b0d12864 --- /dev/null +++ b/docs/src/overview.md @@ -0,0 +1,60 @@ +# Gridap at a glance + +Gridap provides a comprehensive suite of tools for grid-based approximation of partial differential equations (PDEs) in Julia. This page gives you a quick overview of the main capabilities and where to find detailed documentation for each feature. + +## Finite Element Discretizations + +Gridap supports a wide range of finite element discretizations for solving PDEs: + +- **Linear and nonlinear PDE systems** - Handle both simple and complex mathematical problems +- **Transient problems** - Solve time-dependent equations +- **Multi-variable problems** - Couple different physics or solve systems of equations +- **Scalar and tensor variables** - Work with single or multi-component quantities +- **Conforming and nonconforming elements** - Choose from a variety of elements, with arbitrary polynomial order +- **Support for constraints** - Constraints through Lagrange multipliers or linear constraints + +📖 **Learn more**: [Gridap.FESpaces](@ref), [Gridap.MultiField](@ref), [Gridap.ReferenceFEs](@ref), [Gridap.ODEs](@ref) + +## Mesh Support + +Work with flexible mesh representations: + +- **Structured and unstructured meshes** - Regular grids or complex geometries +- **Polytopal meshes** - Triangular/tetrahedral or quadrilateral/hexahedral elements +- **Physical entity labeling** - Tag boundaries and regions for boundary conditions + +📖 **Learn more**: [Gridap.Geometry](@ref) + +## Expandable building blocks + +We provide a modular architecture that allows you seamlessly build your own low-level features: + +- **Tensor algebra** - Efficient tensor operations +- **Polynomials** - Comprehensive polynomial basis library +- **Fields** - Arbitrary operations on local fields +- **Quadratures** - Library of numerical quadratures for integration + +📖 **Learn more**: [Gridap.Polynomials](@ref), [Gridap.TensorValues](@ref), [Gridap.Fields](@ref) + +## Input/Output and Visualization + +Get data in and results out: + +- **VTK support** - Export your results in VTK format for visualization +- **JDL2** - Save and load Gridap objects + +📖 **Learn more**: [Gridap.Io](@ref), [Gridap.Visualization](@ref) + +## Extended Ecosystem + +Gridap's capabilities are greatly extended through companion packages! + +📖 **Learn more**: [Gridap ecosystem](@ref) + +## Performance mode + +Internally, Gridap has many checks to ensure errors are caught early. These are provided through the [`Gridap.Helpers.@check`](@ref) macro, which by default is equivalent to Julia's `@assert`. These checks can however be completely deactivated by through [`Gridap.Helpers.set_execution_mode`](@ref). Note this will recompile the code, so you have to restart Julia after changing the preference. + +```@docs +Gridap +``` diff --git a/ext/TikzPicturesExt.jl b/ext/TikzPicturesExt.jl index b5064a6ba..c3cbdb329 100644 --- a/ext/TikzPicturesExt.jl +++ b/ext/TikzPicturesExt.jl @@ -16,10 +16,11 @@ Available keyword arguments include: - `draw_nodes`: whether to draw visible nodes (default: true) - `draw_labels`: whether to add labels (default: true) -- `label_offset`: offset for labels, w.r.t. nodes (default: 0.2) - `edge_style`: TikZ style for edges (default: "[ ]") - `node_style`: TikZ style for nodes (default: "[circle, fill=black, inner sep=0pt, minimum size=0.1cm]") - `label_style`: TikZ style for labels (default: "[ ]") +- `label_offset`: offset for labels, w.r.t. nodes (default: 0.0) +- `label_dim`: dimension of the labeled faces (default: 0, i.e nodes) as well as any `TikzPicture`-specific keyword arguments. @@ -33,42 +34,47 @@ function TikzPictures.TikzPicture(grid::Grid;kwargs...) end function TikzPictures.TikzPicture(topo::GridTopology;kwargs...) - edge_to_nodes = Geometry.get_faces(topo, 1, 0) - node_coordinates = Geometry.get_vertex_coordinates(topo) - draw_graph(edge_to_nodes, node_coordinates; kwargs...) + draw_topology(topo; kwargs...) end function TikzPictures.TikzPicture(poly::Polytope;kwargs...) - edge_to_nodes = Geometry.get_faces(poly, 1, 0) - node_coordinates = Geometry.get_vertex_coordinates(poly) - draw_graph(edge_to_nodes, node_coordinates; kwargs...) + draw_topology(poly; kwargs...) +end + +function TikzPictures.TikzPicture(rr::Gridap.Adaptivity.RefinementRule;kwargs...) + TikzPicture(rr.reg_grid; kwargs...) end -function draw_graph( - edge_to_nodes, node_coordinates; +function draw_topology( + topo; draw_nodes = true, draw_labels = true, - label_offset = 0.2, edge_style = "[ ]", node_style = "[circle, fill=black, inner sep=0pt, minimum size=0.1cm]", label_style = "[ ]", + label_dim = 0, + label_offset = 0.0, tikz_kwargs... ) - n_edges = length(edge_to_nodes) - n_nodes = length(node_coordinates) + n_nodes = num_vertices(topo) + n_edges = num_faces(topo, 1) + edge_to_nodes = Geometry.get_faces(topo, 1, 0) + node_coordinates = Geometry.get_vertex_coordinates(topo) draw_pt(v) = "(" * join(Tuple(v),",") * ")" draw_edge(e) = draw_edge(edge_to_nodes[e]...) draw_edge(a,b) = "\\draw $(edge_style)" * draw_pt(node_coordinates[a]) * " -- " * draw_pt(node_coordinates[b]) * "; \n" draw_node(a) = "\\node $(node_style) at " * draw_pt(node_coordinates[a]) * " {}; \n" - draw_label(a) = "\\node $(label_style) at " * draw_pt(node_coordinates[a] .+ label_offset) * " { $a }; \n" + draw_label(f,v) = "\\node $(label_style) at " * draw_pt(v) * " { $f }; \n" s = join((draw_edge(e) for e in 1:n_edges), "") if draw_nodes - s *= join((draw_node(a) for a in 1:n_nodes), "") + s *= join((draw_node(a) for a in 1:n_nodes), "") end if draw_labels - s *= join((draw_label(a) for a in 1:n_nodes), "") + face_to_nodes = Geometry.get_faces(topo, label_dim, 0) + face_centroids = map(nodes -> mean(node_coordinates[nodes]) .+ label_offset, face_to_nodes) + s *= join((draw_label(f,v) for (f,v) in enumerate(face_centroids)), "") end return TikzPicture(s;tikz_kwargs...) end diff --git a/src/Adaptivity/Adaptivity.jl b/src/Adaptivity/Adaptivity.jl index cc6a6b03d..7ce8fc14b 100644 --- a/src/Adaptivity/Adaptivity.jl +++ b/src/Adaptivity/Adaptivity.jl @@ -1,5 +1,7 @@ """ Mesh Adaptivity for Gridap + +$(public_names_in_md(@__MODULE__)) """ module Adaptivity @@ -24,6 +26,7 @@ import Gridap.Geometry: get_grid, get_grid_topology, get_face_labeling import Gridap.Geometry: Triangulation, is_change_possible, best_target, get_background_model import Gridap.Geometry: move_contributions import Gridap.CellData: change_domain +import Gridap.ReferenceFEs: Pushforward export RefinementRule export get_cell_map, get_inverse_cell_map @@ -45,7 +48,6 @@ export DorflerMarking, mark, estimate include("RefinementRules.jl") include("FineToCoarseFields.jl") include("OldToNewFields.jl") -include("FineToCoarseReferenceFEs.jl") include("AdaptivityGlues.jl") include("AdaptedDiscreteModels.jl") include("AdaptedTriangulations.jl") diff --git a/src/Adaptivity/MacroFEs.jl b/src/Adaptivity/MacroFEs.jl index 4fd09c1a2..425440bb5 100644 --- a/src/Adaptivity/MacroFEs.jl +++ b/src/Adaptivity/MacroFEs.jl @@ -29,14 +29,14 @@ function Base.getindex(a::FineToCoarseIndices,cid::Integer) end # Note for developpers: -# We use FineToCoarseArray to represent many data-structures, from quadrature points to -# finite-element basis. For performance reasons, we sometimes want to keep a copy of the -# coarse data (but not always!). For instance: -# - for quad points, we want to keep the coarse points. Otherwise, we woudl have to -# recompute them for every cell when the CompositeQuadrature is used with other -# gridap basis. -# - for finite-element basis, we do NOT want to generate the coarse basis, otherwise -# we would be creating it many times (when it should never be used). In this case, we +# We use FineToCoarseArray to represent many data-structures, from quadrature points to +# finite-element basis. For performance reasons, we sometimes want to keep a copy of the +# coarse data (but not always!). For instance: +# - for quad points, we want to keep the coarse points. Otherwise, we woudl have to +# recompute them for every cell when the CompositeQuadrature is used with other +# gridap basis. +# - for finite-element basis, we do NOT want to generate the coarse basis, otherwise +# we would be creating it many times (when it should never be used). In this case, we # create it on the go (see getindex! specialisations). # All in all, this is why coarse_data can be of type Nothing. struct FineToCoarseArray{T,A,B,C} <: AbstractVector{T} @@ -169,7 +169,7 @@ end function Arrays.return_cache(a::MacroDofBasis,b::MacroFEBasis) @check a.rrule == b.rrule caches = map(return_cache,a.fine_data,b.fine_data) - + T = eltype(evaluate!(first(caches),first(a.fine_data),first(b.fine_data))) res = zeros(T,length(a),length(b)) return res, caches @@ -229,26 +229,32 @@ function Arrays.return_cache(a::MacroFEBasis,xc::AbstractArray{<:Point}) k = CoarseToFinePointMap() geo_cache = return_cache(k,rr,xc) xf, ids = evaluate!(geo_cache,k,rr,xc) + xf_cache = array_cache(xf) - eval_caches = map(return_cache,a.fine_data,xf) + # NOTE: xf may be empty for some subcells, so it's safer to use testvalue + xt = testvalue(xc) + eval_caches = map(ffields -> return_cache(ffields,xt),a.fine_data) - T = eltype(evaluate!(first(eval_caches),first(a.fine_data),first(xf))) + T = eltype(evaluate!(first(eval_caches),first(a.fine_data),xt)) res_cache = CachedArray(zeros(T,length(xc),length(a))) - return res_cache, k, geo_cache, eval_caches + return res_cache, k, geo_cache, eval_caches, xf_cache end function Arrays.evaluate!(caches, a::MacroFEBasis,xc::AbstractArray{<:Point}) - res_cache, k, geo_cache, eval_caches = caches + res_cache, k, geo_cache, eval_caches, xf_cache = caches setsize!(res_cache,(length(xc),length(a))) res = res_cache.array fill!(res,zero(eltype(res))) xf, ids = evaluate!(geo_cache,k,a.rrule,xc) - + for fcell in 1:num_subcells(a.rrule) - r = xf.ptrs[fcell]:xf.ptrs[fcell+1]-1 - vals = evaluate!(eval_caches[fcell],a.fine_data[fcell],view(xf.data,r)) - I = view(ids.data,r) + xf_k = getindex!(xf_cache,xf,fcell) + if isempty(xf_k) + continue + end + vals = evaluate!(eval_caches[fcell],a.fine_data[fcell],xf_k) + I = view(ids,fcell) J = a.ids.fcell_to_cids[fcell] res[I,J] .= vals end @@ -270,7 +276,7 @@ end function Arrays.return_cache(a::MacroFEBasis,b::FineToCoarseArray{<:Point}) @check a.rrule == b.rrule caches = map(return_cache,a.fine_data,b.fine_data) - + T = eltype(evaluate!(first(caches),first(a.fine_data),first(b.fine_data))) res_cache = CachedArray(zeros(T,length(b),length(a))) return res_cache, caches @@ -397,8 +403,8 @@ end """ get_cface_to_own_dofs( - rrule::RefinementRule, - space::FESpace, + rrule::RefinementRule, + space::FESpace, reffes::AbstractVector{<:ReferenceFE} ) @@ -418,8 +424,8 @@ end """ get_cface_to_dofs( - rrule::RefinementRule, - space::FESpace, + rrule::RefinementRule, + space::FESpace, reffes::AbstractVector{<:ReferenceFE} ) @@ -459,7 +465,7 @@ function get_cface_to_own_fface_to_own_dofs( space::FESpace, reffes::AbstractVector{<:ReferenceFE} ) - cface_to_own_fface_to_own_dofs, _ = + cface_to_own_fface_to_own_dofs, _ = _compute_cface_to_own_fface_to_own_dofs_and_permutations(rrule,space,reffes) return cface_to_own_fface_to_own_dofs end @@ -475,7 +481,7 @@ function _compute_cface_to_own_fface_to_own_dofs_and_permutations( ) where Dc poly = get_polytope(rrule) topo = get_grid_topology(rrule.ref_grid) - + coffsets = get_offsets(poly) foffsets = get_offsets(topo) @@ -497,7 +503,7 @@ function _compute_cface_to_own_fface_to_own_dofs_and_permutations( # We need to collect the dofs for each fine face, but the owned dofs are given cell-wise. # So we need to iterate over the cells and identify which fine face we are looking at... - # Since we are going to see some faces more than once, we keep track of which ones we + # Since we are going to see some faces more than once, we keep track of which ones we # have already seen through the `touched` array. # We also collect local dof permutations on that face, which will be aggregated later. cface_to_own_fface_to_dofs = [ @@ -548,7 +554,7 @@ end reffes::AbstractVector{<:ReferenceFE} ) -Given a RefinementRule and information on the dofs owned by each fine face, compute +Given a RefinementRule and information on the dofs owned by each fine face, compute the permutations of the dofs owned by each coarse face. """ function get_cface_to_own_dof_permutations( @@ -557,12 +563,12 @@ function get_cface_to_own_dof_permutations( reffes::AbstractVector{<:ReferenceFE} ) # Dof permutation data - cface_to_fface_to_dofs, - cface_to_fface_to_fpindex_to_ldofs = + cface_to_fface_to_dofs, + cface_to_fface_to_fpindex_to_ldofs = _compute_cface_to_own_fface_to_own_dofs_and_permutations(rrule,space,reffes) - # We need to convert the dof numberings (which are in the rrule numeration) into - # the local numeration of the coarse faces. + # We need to convert the dof numberings (which are in the rrule numeration) into + # the local numeration of the coarse faces. cface_to_fface_to_ldofs = map(cface_to_fface_to_dofs) do fface_to_dofs fface_to_ldofs = Vector{Vector{Int}}(undef,length(fface_to_dofs)) offset = 0 @@ -575,7 +581,7 @@ function get_cface_to_own_dof_permutations( end # Topological permutation data - cface_to_cpindex_to_ffaces, + cface_to_cpindex_to_ffaces, cface_to_cpindex_to_fpindex = get_cface_to_own_fface_permutations(rrule) # Allocate output @@ -594,8 +600,8 @@ function get_cface_to_own_dof_permutations( p_ffaces = cface_to_cpindex_to_ffaces[cface][cpindex] # Permuted fine faces fface_pindex = cface_to_cpindex_to_fpindex[cface][cpindex] # Local fine permutation index - # Collect dofs for each fine face owned by the coarse face, - # and permute them according to the two-level permutation induced by + # Collect dofs for each fine face owned by the coarse face, + # and permute them according to the two-level permutation induced by # the coarse permutation (see `get_cface_to_own_fface_permutations`) dofs = Int32[] for (p_fface,fpindex) in zip(p_ffaces,fface_pindex) @@ -608,4 +614,4 @@ function get_cface_to_own_dof_permutations( end return cface_to_cpindex_to_dofs -end \ No newline at end of file +end diff --git a/src/Adaptivity/FineToCoarseReferenceFEs.jl b/src/Adaptivity/deprecated/FineToCoarseReferenceFEs.jl similarity index 100% rename from src/Adaptivity/FineToCoarseReferenceFEs.jl rename to src/Adaptivity/deprecated/FineToCoarseReferenceFEs.jl diff --git a/src/Algebra/Algebra.jl b/src/Algebra/Algebra.jl index 46a520f35..87961270d 100644 --- a/src/Algebra/Algebra.jl +++ b/src/Algebra/Algebra.jl @@ -1,7 +1,6 @@ """ -The exported names are -$(EXPORTS) +$(public_names_in_md(@__MODULE__)) """ module Algebra diff --git a/src/Algebra/AlgebraInterfaces.jl b/src/Algebra/AlgebraInterfaces.jl index d3919cfb4..6f419bda6 100644 --- a/src/Algebra/AlgebraInterfaces.jl +++ b/src/Algebra/AlgebraInterfaces.jl @@ -335,8 +335,23 @@ end # add_entries!(c,vs,is,js) # +""" + struct Loop end +""" struct Loop end +""" + struct DoNotLoop end +""" struct DoNotLoop end +""" + LoopStyle(::Type) + LoopStyle(::T) where T = LoopStyle(T) + +Trait to tell if looping on nonzeros entries is necessary to count the values +with a counter (see [`nz_counter`](@ref)) of an array of type `T`. + +Returns [`Loop()`](@ref Loop) or [`DoNotLoop`](@ref). +""" LoopStyle(::Type) = DoNotLoop() LoopStyle(::T) where T = LoopStyle(T) @@ -357,16 +372,30 @@ end # For dense arrays +""" + struct ArrayBuilder{T} + +`T` is the type of array to be built. +""" struct ArrayBuilder{T} array_type::Type{T} end ArrayBuilder(a::ArrayBuilder) = a +""" + get_array_type(::ArrayBuilder{T}) = T +""" get_array_type(::ArrayBuilder{T}) where T = T +""" + ArrayCounter{T}(axes::A) + +where `T` is the target matrix/array type. +""" struct ArrayCounter{T,A} axes::A + function ArrayCounter{T}(axes::A) where {T,A<:Tuple{Vararg{AbstractUnitRange}}} new{T,A}(axes) end @@ -380,27 +409,66 @@ LoopStyle(::Type{<:ArrayCounter}) = DoNotLoop() @inline add_entries!(c::Function,a::ArrayCounter,v,i) = a #nz_counter(::Type{T},axes) where T = ArrayCounter{T}(axes) + +""" + nz_counter(::ArrayBuilder{T},axes) + +Generate a counter ([`ArrayCounter`](@ref)) to count the nonzero values for an array type A. +""" nz_counter(::ArrayBuilder{T},axes) where T = ArrayCounter{T}(axes) +""" + nz_allocation(a::ArrayCounter{T}) + +Allocates a vector that will serve as structural nonzero values internal storage +for a matrix holding the values counted in `a`. See also +[`create_from_nz`](@ref), `SparseArrays.nonzeros`. +""" nz_allocation(a::ArrayCounter{T}) where T = fill!(similar(T,map(length,a.axes)),zero(eltype(T))) +""" + create_from_nz(nz_alloc::AbstractArray) + +Creates a matrix from its nonzero values, allocated using [`nz_allocation`](@ref) +and filled using [`add_entry!`](@ref) and/or [`add_entries!`](@ref). +""" create_from_nz(a::AbstractArray) = a # For sparse matrices +""" + struct MinCPU end + +Represent sparse matrix assembly strategy minimizing CPU cost. +""" struct MinCPU end +""" + struct MinMemory{T} + +Represent sparse matrix assembly strategy minimizing memory cost. +""" struct MinMemory{T} maxnnz::T end MinMemory() = MinMemory(nothing) +""" + struct SparseMatrixBuilder{T,A} +""" struct SparseMatrixBuilder{T,A} matrix_type::Type{T} approach::A end +""" + SparseMatrixBuilder(::Type{T}[, ::S]) + +Create a builder for sparse matrix of type `T`, using assembly stategy `S()`, +either [`MinCPU()`](@ref MinCPU) or [`MinMemory()`](@ref MinMemory). +The default is `MinMemory()`. +""" SparseMatrixBuilder(::Type{T}) where T = SparseMatrixBuilder(T,MinMemory()) SparseMatrixBuilder(a::SparseMatrixBuilder) = a @@ -502,6 +570,12 @@ function finalize_coo!(::Type,I,J,V,m,n) @abstractmethod end +""" + nz_index(A::AbstractSparseMatrix,i,j) + +Index of `A`[`i`,`j`] in the structural nonzero values internal storage of `A`. +See also `SparseArrays.nonzeros`. +""" function nz_index(A::AbstractSparseMatrix,i,j) @abstractmethod end @@ -530,6 +604,9 @@ function copy_entries!(a::T,b::T) where T<:AbstractSparseMatrix end end +""" + allocate_coo_vectors(::Type{<:AbstractSparseMatrix{Tv,Ti}}, n::Integer) +""" function allocate_coo_vectors( ::Type{<:AbstractSparseMatrix{Tv,Ti}},n::Integer) where {Tv,Ti} (zeros(Ti,n), zeros(Ti,n), zeros(Tv,n)) diff --git a/src/Algebra/NonlinearOperators.jl b/src/Algebra/NonlinearOperators.jl index ad3835284..7c68ce5a5 100644 --- a/src/Algebra/NonlinearOperators.jl +++ b/src/Algebra/NonlinearOperators.jl @@ -63,7 +63,12 @@ function residual_and_jacobian(op::NonlinearOperator,x::AbstractVector) (b, A) end +""" + function hessian end +""" function hessian end +""" +""" function hessian! end """ diff --git a/src/Arrays/AlgebraMaps.jl b/src/Arrays/AlgebraMaps.jl index 43651a9d5..a80d3b367 100644 --- a/src/Arrays/AlgebraMaps.jl +++ b/src/Arrays/AlgebraMaps.jl @@ -56,6 +56,11 @@ end # MulAddMap: Cached version of `mul!(d,a,b,α,β)` +""" + struct MulAddMap{T} <: Map + +Map for operation a,b,c -> LinearAlgepra.mul!(copy(c),a,b,`α`,`β`) +""" struct MulAddMap{T} <: Map α::T β::T @@ -99,6 +104,11 @@ end # Assembly Maps: AddEntriesMap and TouchEntriesMap +""" + struct AddEntriesMap{F} <: Map + +[`Map`](@ref) for [`add_entries!`](@ref) where `F` is the combine function. +""" struct AddEntriesMap{F} <: Map combine::F end @@ -111,6 +121,12 @@ function evaluate!(cache,k::AddEntriesMap,A,v,i) add_entries!(k.combine,A,v,i) end +""" + struct TouchEntriesMap <: Map + +Dummy [`AddEntriesMap`](@ref) used to keep track of sparsity patterns in symbolic +assemblies, see the symbolic methods of [`SparseMatrixAssembler`](@ref Gridap.FESpaces.SparseMatrixAssembler). +""" struct TouchEntriesMap <: Map end function evaluate!(cache,k::TouchEntriesMap,A,v,i,j) @@ -248,10 +264,10 @@ end # end for T A map for solving local linear systems, relying on a factorization method. -Given a left-hand-side matrix `mat` and a set of N right-hand-side arrays `lhs`, -returns an N-Tuple of arrays containing the solutions to the linear systems defined by +Given a left-hand-side matrix `mat` and a set of N right-hand-side arrays `lhs`, +returns an N-Tuple of arrays containing the solutions to the linear systems defined by -Each system is given by `A*x_i = b_i`, and the solution is computed as +Each system is given by `A*x_i = b_i`, and the solution is computed as `x_i = ldiv!(factorize!(A,pivot),b_i)` """ @@ -321,12 +337,12 @@ end A map for solving local constrained linear systems, relying on a factorization method. -Given a left-hand-side 2x2 block matrix matrix`mat` and a set of 2xN right-hand-side arrays `lhs`, -returns an N-Tuple of arrays containing the solutions to the linear systems. +Given a left-hand-side 2x2 block matrix matrix`mat` and a set of 2xN right-hand-side arrays `lhs`, +returns an N-Tuple of arrays containing the solutions to the linear systems. Each system is given by `A*[x_i; λ_i] = b_i`, where `A = [App, Aλp; Apλ, 0]` is the -augmented matrix, and `b_i = [Bp; Bλ]` is the right-hand side vector. The solution is -computed using a penalty method, as `x_i = ldiv!(factorize!(C,pivot),d_i)` with +augmented matrix, and `b_i = [Bp; Bλ]` is the right-hand side vector. The solution is +computed using a penalty method, as `x_i = ldiv!(factorize!(C,pivot),d_i)` with `C = App + μT * Apλ * Aλp` and `d_i = Bp + μT * Apλ * Bλ`, where `μT` is a penalty parameter. The penalty parameter μT is heuristically chosen as `μT = norm(App)/norm(Apλ*Aλp)`. @@ -356,10 +372,10 @@ function Arrays.evaluate!(cache::Nothing, k::LocalPenaltySolveMap, lhs, rhs::Vec else μT = tr(App)/norm(Apλ*Aλp) # Multiple constraints end - + # App = App + μT * Apλ * Aλp mul!(App, Apλ, Aλp, μT, 1) - + # Bp = Bp + μT * Apλ * Bλ mul!(Bp, Apλ, Bλ, μT, 1) @@ -383,10 +399,10 @@ function Arrays.evaluate!(cache::Nothing, k::LocalPenaltySolveMap, lhs, rhs) else μT = tr(App)/norm(Apλ*Aλp) # Multiple constraints end - + # App = App + μT * Apλ * Aλp mul!(App, Apλ, Aλp, μT, 1) - + # BpΩ = BpΩ + μT * Apλ * BλΩ mul!(BpΩ, Apλ, BλΩ, μT, 1) diff --git a/src/Arrays/AppendedArrays.jl b/src/Arrays/AppendedArrays.jl index c44e37007..58790f34c 100644 --- a/src/Arrays/AppendedArrays.jl +++ b/src/Arrays/AppendedArrays.jl @@ -55,9 +55,20 @@ function _compact_values_ptrs(a) end end +""" + struct AppendedArray{T,A,B} <: AbstractVector{T} + +Type for a lazily appended array. +""" struct AppendedArray{T,A,B} <: AbstractVector{T} a::A b::B + + """ + AppendedArray(a::AbstractArray,b::AbstractArray) + + Return a [`AppendedArray`](@ref). + """ function AppendedArray(a::AbstractArray,b::AbstractArray) A = typeof(a) B = typeof(b) diff --git a/src/Arrays/ArrayBlocks.jl b/src/Arrays/ArrayBlocks.jl index 068488555..180374961 100644 --- a/src/Arrays/ArrayBlocks.jl +++ b/src/Arrays/ArrayBlocks.jl @@ -45,7 +45,17 @@ end get_array(b::ArrayBlock) = b.array +""" + const VectorBlock = ArrayBlock{A,1} + +Alias, see also [`ArrayBlock`](@ref). +""" const VectorBlock = ArrayBlock{A,1} where A +""" + const MatrixBlock = ArrayBlock{A,2} + +Alias, see also [`ArrayBlock`](@ref). +""" const MatrixBlock = ArrayBlock{A,2} where A Base.axes(b::ArrayBlock,i) = axes(b.array,i) @@ -788,15 +798,30 @@ end # ArrayBlock views +""" + struct ArrayBlockView{A,N,M} + +Low level container implementing view on [`ArrayBlock`](@ref). +""" struct ArrayBlockView{A,N,M} array::ArrayBlock{A,M} block_map::Array{CartesianIndex{M},N} end -Base.view(a::ArrayBlock{A,M},b::Array{CartesianIndex{M},N}) where {A,M,N} = ArrayBlockView(a,b) +""" + const MatrixBlockView{A} = ArrayBlockView{A,2,2} + +Alias, see also [`ArrayBlockView`](@ref). +""" const MatrixBlockView{A} = ArrayBlockView{A,2,2} where A +""" + const VectorBlockView{A} = ArrayBlockView{A,1,1} + +Alias, see also [`ArrayBlockView`](@ref). +""" const VectorBlockView{A} = ArrayBlockView{A,1,1} where A +Base.view(a::ArrayBlock{A,M},b::Array{CartesianIndex{M},N}) where {A,M,N} = ArrayBlockView(a,b) Base.axes(a::ArrayBlockView,i) = axes(a.block_map,i) Base.size(a::ArrayBlockView) = size(a.block_map) Base.length(b::ArrayBlockView) = length(b.block_map) diff --git a/src/Arrays/Arrays.jl b/src/Arrays/Arrays.jl index 8041172af..fff081fe4 100644 --- a/src/Arrays/Arrays.jl +++ b/src/Arrays/Arrays.jl @@ -6,7 +6,7 @@ This module provides: The exported names in this module are: -$(EXPORTS) +$(public_names_in_md(@__MODULE__)) """ module Arrays @@ -53,6 +53,7 @@ export inverse_map export Broadcasting export Operation +export InverseMap # LazyArray @@ -179,6 +180,4 @@ include("Autodiff.jl") include("PrintOpTrees.jl") -const ∑ = sum - end # module diff --git a/src/Arrays/Autodiff.jl b/src/Arrays/Autodiff.jl index fb1d2fec5..988f3a4a6 100644 --- a/src/Arrays/Autodiff.jl +++ b/src/Arrays/Autodiff.jl @@ -1,4 +1,6 @@ +""" +""" function autodiff_array_gradient(a,i_to_x) dummy_tag = ()->() i_to_cfg = lazy_map(ConfigMap(ForwardDiff.gradient,dummy_tag),i_to_x) @@ -8,6 +10,8 @@ function autodiff_array_gradient(a,i_to_x) i_to_result end +""" +""" function autodiff_array_jacobian(a,i_to_x) dummy_tag = ()->() i_to_cfg = lazy_map(ConfigMap(ForwardDiff.jacobian,dummy_tag),i_to_x) @@ -17,6 +21,8 @@ function autodiff_array_jacobian(a,i_to_x) i_to_result end +""" +""" function autodiff_array_hessian(a,i_to_x) agrad = i_to_y -> autodiff_array_gradient(a,i_to_y) autodiff_array_jacobian(agrad,i_to_x) @@ -58,6 +64,12 @@ function autodiff_array_reindex(i_to_val, j_to_i) return j_to_val end +""" + struct ConfigMap{F,T} <: Map + +Map for ForwardDiff.[`F`]Config(`T`,...) where `T` is tag function and `F` is +either gradient or jacobian. +""" struct ConfigMap{ F <: Union{typeof(ForwardDiff.gradient),typeof(ForwardDiff.jacobian)}, T <: Union{<:Function,Nothing}} <: Map @@ -83,6 +95,9 @@ function evaluate!(cfg,k::ConfigMap,x) cfg end +""" + struct DualizeMap <: Map +""" struct DualizeMap <: Map end function evaluate!(cache,::DualizeMap,cfg,x) @@ -91,6 +106,9 @@ function evaluate!(cache,::DualizeMap,cfg,x) xdual end +""" + struct AutoDiffMap <: Map +""" struct AutoDiffMap <: Map end function return_cache(::AutoDiffMap,cfg::ForwardDiff.GradientConfig,ydual) diff --git a/src/Arrays/CachedArrays.jl b/src/Arrays/CachedArrays.jl index 06231d726..8982a2319 100644 --- a/src/Arrays/CachedArrays.jl +++ b/src/Arrays/CachedArrays.jl @@ -27,7 +27,7 @@ mutable struct CachedArray{T,N,A<:AbstractArray{T,N}} <: AbstractArray{T,N} @doc """ CachedArray(a::AbstractArray) - + Constructs a `CachedArray` from a given array. """ function CachedArray(array::A) where {T,N,A<:AbstractArray{T,N}} diff --git a/src/Arrays/CompressedArrays.jl b/src/Arrays/CompressedArrays.jl index b24497264..24262a133 100644 --- a/src/Arrays/CompressedArrays.jl +++ b/src/Arrays/CompressedArrays.jl @@ -4,6 +4,7 @@ values::A ptrs::P end + Type representing an array with a reduced set of values. The array is represented by a short array of values, namely the field `values`, and a large array of indices, namely the @@ -108,4 +109,4 @@ end function Base.view(a::CompressedArray,ids) CompressedArray(a.values,view(a.ptrs,ids)) -end \ No newline at end of file +end diff --git a/src/Arrays/FilteredArrays.jl b/src/Arrays/FilteredArrays.jl index 9d4842ea0..9363d189f 100644 --- a/src/Arrays/FilteredArrays.jl +++ b/src/Arrays/FilteredArrays.jl @@ -1,4 +1,7 @@ +# This seems to be unused. +""" +""" struct FilterMap <: Map end function return_cache(k::FilterMap,f,a) diff --git a/src/Arrays/IdentityVectors.jl b/src/Arrays/IdentityVectors.jl index 83163a46c..adb9122ce 100644 --- a/src/Arrays/IdentityVectors.jl +++ b/src/Arrays/IdentityVectors.jl @@ -5,11 +5,9 @@ struct IdentityVector{T<:Integer} <: AbstractVector{T} length::T end -function getindex(c::IdentityVector{T},i::Integer) where T - @check i > 0 - @check i <= c.length - j::T = i - j +@propagate_inbounds function getindex(c::IdentityVector{T},i::Integer) where T + @boundscheck @check 0 < i <= c.length + return T(i) end size(c::IdentityVector) = (c.length,) diff --git a/src/Arrays/Interface.jl b/src/Arrays/Interface.jl index 66ee7d713..86cb4a582 100644 --- a/src/Arrays/Interface.jl +++ b/src/Arrays/Interface.jl @@ -294,3 +294,8 @@ function test_array( end true end + +""" +Alias for `Base.sum`. +""" +const ∑ = sum diff --git a/src/Arrays/KeyToValMaps.jl b/src/Arrays/KeyToValMaps.jl index 80902e49c..b666027ab 100644 --- a/src/Arrays/KeyToValMaps.jl +++ b/src/Arrays/KeyToValMaps.jl @@ -1,3 +1,8 @@ +""" + struct KeyToValMap{T<:Function} <: Map + +Map for lazily filling a `Dict` the outputs of the function `T` over an array of inputs. +""" struct KeyToValMap{T<:Function} <: Map key_to_val::T end diff --git a/src/Arrays/Maps.jl b/src/Arrays/Maps.jl index 7552b80c8..deb493c0c 100644 --- a/src/Arrays/Maps.jl +++ b/src/Arrays/Maps.jl @@ -61,6 +61,11 @@ arguments of the types of the objects `x`. """ return_type(f,x...) = typeof(return_value(f,x...)) +""" + return_value(f,x...) + +Return a variable of the type of the image fx=`f`(`x`...) (possibly fx itself). +""" return_value(f,x...) = evaluate(f,testargs(f,x...)...) """ @@ -296,3 +301,21 @@ function inverse_map(f) Function inverse_map is not implemented yet for objects of type $(typeof(f)) """ end + +""" + struct InverseMap{F} <: Map + +Map for the inverse of the `Function` or [`Map`](@ref) `F`. +""" +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 diff --git a/src/Arrays/PrintOpTrees.jl b/src/Arrays/PrintOpTrees.jl index b4791f678..f53976995 100644 --- a/src/Arrays/PrintOpTrees.jl +++ b/src/Arrays/PrintOpTrees.jl @@ -1,9 +1,14 @@ +""" +""" struct TreeNode{T} node::T showid::Bool end +""" + similar_tree_node(n::TreeNode,node) +""" similar_tree_node(n::TreeNode,node) = TreeNode(node,n.showid) AbstractTrees.children(a::TreeNode) = get_children(a,a.node) @@ -30,6 +35,12 @@ function print_node(io,n::TreeNode,a) end end +""" + print_op_tree(a,args...;kwargs...) + print_op_tree(io::IO,a,args...;showid=false,kwargs...) + +Print the operation tree of a lazy operation/array, in `stdout` if `io` is not given. +""" function print_op_tree(a,args...;kwargs...) print_op_tree(stdout,a,args...;kwargs...) end diff --git a/src/Arrays/Tables.jl b/src/Arrays/Tables.jl index 452f4cdf5..3acb0505e 100644 --- a/src/Arrays/Tables.jl +++ b/src/Arrays/Tables.jl @@ -198,7 +198,7 @@ end """ append_ptrs(pa,pb) -Append two vectors of pointers. +Concatenate two vectors of pointers in a new vector. """ function append_ptrs(pa::AbstractVector{T},pb::AbstractVector{T}) where T p = copy(pa) @@ -206,6 +206,10 @@ function append_ptrs(pa::AbstractVector{T},pb::AbstractVector{T}) where T end """ + append_ptrs!(pa,pb) + +Similar to [`append_ptrs`](@ref), but appends `pb` at the end of `pa`, in place +in `pa`. """ function append_ptrs!(pa::AbstractVector{T},pb::AbstractVector{T}) where T na = length(pa)-1 @@ -233,12 +237,12 @@ function _append_count!(pa,pb,na,nb) end """ + const UNSET = 0 """ const UNSET = 0 """ - find_inverse_index_map(a_to_b[, nb=maximum(a_to_b)]) - find_inverse_index_map!(b_to_a, a_to_b) + find_inverse_index_map(a_to_b, nb=maximum(a_to_b)) Given a vector of indices `a_to_b`, returns the inverse index map `b_to_a`. """ @@ -249,6 +253,11 @@ function find_inverse_index_map(a_to_b, nb=maximum(a_to_b)) b_to_a end +""" + find_inverse_index_map!(b_to_a, a_to_b) + +In place [`find_inverse_index_map`](@ref). +""" function find_inverse_index_map!(b_to_a, a_to_b) for (a,b) in enumerate(a_to_b) if b != UNSET @@ -314,6 +323,7 @@ function inverse_table( end """ + append_tables_globally(tables::Table...) """ function append_tables_globally( first_table::Table{T,Vd,Vp},tables::Table{T,Vd,Vp}... @@ -336,8 +346,6 @@ function append_tables_locally(tables::Table...) append_tables_locally(offsets,tables) end -""" -""" function append_tables_locally(offsets::NTuple, tables::NTuple) @check length(offsets) == length(tables) !== 0 "Offsets and tables must have the same length" first_table, = tables @@ -419,6 +427,11 @@ function lazy_map(::typeof(getindex),a::Table,b::AbstractArray{<:Integer}) LocalItemFromTable(a,b) end +""" + get_local_item(a::Table,li::Integer) + +View in the `li`ᵗʰ column of `a` (the `li`ᵗʰ items in each list/row of `a`). +""" function get_local_item(a::Table,li::Integer) LocalItemFromTable(a,Fill(li,length(a))) end @@ -587,19 +600,19 @@ end """ merge_entries(a_to_lb_to_b, c_to_la_to_a) -> c_to_lb_to_b -Merge the entries of `a_to_lb_to_b`, grouping them by `c_to_la_to_a`. Returns +Merge the entries of `a_to_lb_to_b`, grouping them by `c_to_la_to_a`. Returns the merged table `c_to_lb_to_b`. Accepts the following keyword arguments: -- `acc`: Accumulator for the entries of `a_to_lb_to_b`. Default to a `Set`, ensuring +- `acc`: Accumulator for the entries of `a_to_lb_to_b`. Default to a `Set`, ensuring that the resulting entries are unique. - `post`: Postprocessing function to apply to the accumulator before storing the resulting entries. Defaults to the identity, but can be used to perform local sorts or filters, for example. """ function merge_entries( a_to_lb_to_b::AbstractVector{<:AbstractVector{T}}, - c_to_la_to_a::AbstractVector{<:AbstractVector{Ti}}; + c_to_la_to_a::AbstractVector{<:AbstractVector{Ti}}; acc = Set{T}(), post = identity ) where {T,Ti<:Integer} @@ -637,7 +650,7 @@ end """ block_identity_array(ptrs;T=Int) -Given a vector of pointers of length `n+1`, returns a vector of length `ptrs[end]-1` +Given a vector of pointers of length `n+1`, returns a vector of length `ptrs[end]-1` where the entries are the index of the block to which each entry belongs. # Example diff --git a/src/Arrays/VectorsWithEntryInserted.jl b/src/Arrays/VectorsWithEntryInserted.jl index 22ffaf6b6..c9ee9b609 100644 --- a/src/Arrays/VectorsWithEntryInserted.jl +++ b/src/Arrays/VectorsWithEntryInserted.jl @@ -1,7 +1,18 @@ +""" + mutable struct VectorWithEntryInserted{T,A} <: AbstractVector{T} +""" mutable struct VectorWithEntryInserted{T,A} <: AbstractVector{T} a::A index::Int value::T + + """ + VectorWithEntryInserted(a::AbstractVector,index::Integer,value) + + Return a `VectorWithEntryInserted` that is `a` but with `value` lazily + inserted at position `index` (without memory reallocation). + Beware, `a` is not copied, only referenced. + """ function VectorWithEntryInserted(a::AbstractVector,index::Integer,value) A = typeof(a) T = eltype(a) diff --git a/src/Arrays/VectorsWithEntryRemoved.jl b/src/Arrays/VectorsWithEntryRemoved.jl index 17d5ecaf0..a795b62d7 100644 --- a/src/Arrays/VectorsWithEntryRemoved.jl +++ b/src/Arrays/VectorsWithEntryRemoved.jl @@ -1,6 +1,16 @@ +""" + struct VectorWithEntryRemoved{T,A} <: AbstractVector{T} +""" struct VectorWithEntryRemoved{T,A} <: AbstractVector{T} a::A index::Int + + """ + VectorWithEntryRemoved(a::AbstractVector,index::Integer) + + Return a `VectorWithEntryRemoved` that is `a` but with the `index`'s entry lazily removed. + Beware, `a` is not copied, only referenced. + """ function VectorWithEntryRemoved(a::AbstractVector,index::Integer) A = typeof(a) T = eltype(a) diff --git a/src/CellData/AttachConstraints.jl b/src/CellData/AttachConstraints.jl index 41e5e0866..8f58f57ea 100644 --- a/src/CellData/AttachConstraints.jl +++ b/src/CellData/AttachConstraints.jl @@ -1,8 +1,14 @@ +""" + attach_constraints_rows(cellvec, cellconstr, cellmask=(true,...)) +""" function attach_constraints_rows(cellvec,cellconstr,cellmask=Fill(true,length(cellconstr))) lazy_map(ConstrainRowsMap(),cellvec,cellconstr,cellmask) end +""" + attach_constraints_cols(cellmat, cellconstr, cellmask=Fill(true,...)) +""" function attach_constraints_cols(cellmat,cellconstr,cellmask=Fill(true,length(cellconstr))) cellconstr_t = lazy_map(transpose,cellconstr) lazy_map(ConstrainColsMap(),cellmat,cellconstr_t,cellmask) @@ -70,6 +76,11 @@ function Arrays.evaluate!(cache,k::ConstrainColsMap,matvec::Tuple,constr_t,mask) end end +""" + identity_constraints(cell_axes) + +Dummy constraints for Unconstrained spaces. +""" function identity_constraints(cell_axes) lazy_map(IdentityConstraintMap(),cell_axes) end diff --git a/src/CellData/AttachDirichlet.jl b/src/CellData/AttachDirichlet.jl index 4a72760f0..c3ac90810 100644 --- a/src/CellData/AttachDirichlet.jl +++ b/src/CellData/AttachDirichlet.jl @@ -1,4 +1,7 @@ +""" + attach_dirichlet(cellmatvec, cellvals, cellmask=(true,...)) +""" function attach_dirichlet(cellmatvec,cellvals,cellmask=Fill(true,length(cellvals))) k = AttachDirichletMap() lazy_map(k,cellmatvec,cellvals,cellmask) diff --git a/src/CellData/CellData.jl b/src/CellData/CellData.jl index dbb86eec7..1c4d47525 100644 --- a/src/CellData/CellData.jl +++ b/src/CellData/CellData.jl @@ -1,7 +1,16 @@ """ - -The exported names are -$(EXPORTS) +$(public_names_in_md(@__MODULE__; change_link=Dict( + :∇ => "gradient", + :∫ => "Integrand", + :⊗ => "Gridap.TensorValues.outer", + :⊙ => "Gridap.TensorValues.inner", + :× => "cross", + :⋅ => "dot", + :⋅¹ => "dot", + :⋅² => "Gridap.TensorValues.double_contraction", + :ReferenceDomain => "DomainStyle", + :PhysicalDomain => "DomainStyle" +))) """ module CellData @@ -88,7 +97,7 @@ export update_state! export DiracDelta -export SkeletonCellFieldPair +export SkeletonCellFieldPair include("CellDataInterface.jl") diff --git a/src/CellData/CellDataInterface.jl b/src/CellData/CellDataInterface.jl index e711036fa..842a4b2c4 100644 --- a/src/CellData/CellDataInterface.jl +++ b/src/CellData/CellDataInterface.jl @@ -1,12 +1,18 @@ """ -Trait that signals if a CellDatum type is implemented in the physical or the reference domain + abstract type DomainStyle + +Trait that signals if a CellDatum type is implemented in the physical or the +reference domain, the possible values are `ReferenceDomain()` and +`PhysicalDomain()`. """ abstract type DomainStyle end struct ReferenceDomain <: DomainStyle end struct PhysicalDomain <: DomainStyle end """ + abstract type CellDatum <: GridapType + Data associated with the cells of a Triangulation. CellDatum objects behave as if they are defined in the physical space of the triangulation. But in some cases they are implemented as reference quantities plus some transformation to the physical domain. @@ -14,21 +20,31 @@ But in some cases they are implemented as reference quantities plus some transfo abstract type CellDatum <: GridapType end """ + get_data(a::CellDatum) + Get the stored array of cell-wise data. It can be defined in the physical or the reference domain. """ get_data(a::CellDatum) = @abstractmethod """ -Tell if the stored array is in the reference or physical domain + DomainStyle(::Type{<:CellDatum}) + +Tell if the stored array is in the reference or physical domain. """ DomainStyle(::Type{<:CellDatum}) = @abstractmethod """ -Return the underlying Triangulation object + get_triangulation(a::CellDatum) + +Return the underlying Triangulation object. """ get_triangulation(a::CellDatum) = @abstractmethod """ + change_domain(a::CellDatum, target_domain) + change_domain(a::CellDatum, input_domain, target_domain) + +where `a` isa [`CellDatum`](@ref) and the domains are [`DomainStyle`](@ref)s. Change the underlying data to the target domain """ change_domain(a::CellDatum,target_domain::DomainStyle) = change_domain(a,DomainStyle(a),target_domain) @@ -49,10 +65,15 @@ end DomainStyle(::T) where T<:CellDatum = DomainStyle(T) """ -Get the raw array of cell data defined in the physical space. + get_array(a::CellDatum) + +Get the raw array of `a` datas defined in the physical space. """ get_array(a::CellDatum) = get_data(change_domain(a,PhysicalDomain())) """ + num_cells(a::CellDatum) + +Number of cells of `a`. """ num_cells(a::CellDatum) = num_cells(get_triangulation(a)) diff --git a/src/CellData/CellFields.jl b/src/CellData/CellFields.jl index 33fa255ea..6c897410e 100644 --- a/src/CellData/CellFields.jl +++ b/src/CellData/CellFields.jl @@ -115,11 +115,17 @@ function CellField(f,trian::Triangulation) CellField(f,trian,ReferenceDomain()) end +""" + get_normal_vector(trian::Triangulation) +""" function get_normal_vector(trian::Triangulation) cell_normal = get_facet_normal(trian) get_normal_vector(trian, cell_normal) end +""" + get_tangent_vector(trian::Triangulation) +""" function get_tangent_vector(trian::Triangulation) cell_tangent = get_edge_tangent(trian) get_tangent_vector(trian, cell_tangent) @@ -624,9 +630,24 @@ function evaluate!(cache,k::Operation,a::SkeletonPair{<:CellField},b::SkeletonPa SkeletonPair(plus,minus) end +""" + jump(a::CellField) + jump(a::SkeletonPair{<:CellField}) + +Jump operator at interior facets of the supporting `Triangulation`, defined by +`jump`(`a` n) = ⟦`a` n⟧ = `a`⁺n⁺ + `a`⁻n⁻, where n is an oriented normal field +to the interior facets, n⁺ = -n⁻ are the normal pointing into the element on the + +and - side of the facets, and `a`⁺/`a`⁻ are the restrictions of `a` to each +element respectively. +""" jump(a::CellField) = a.⁺ - a.⁻ jump(a::SkeletonPair{<:CellField}) = a.⁺ + a.⁻ # a.⁻ results from multiplying by n.⁻. Thus we need to sum. +""" + mean(a::CellField) + +Similar to [`jump`](@ref), but for the mean operator `a` ⟶ (`a`⁺ + `a`⁻)/2. +""" mean(a::CellField) = Operation(_mean)(a.⁺,a.⁻) _mean(x,y) = 0.5*x + 0.5*y diff --git a/src/CellData/CellQuadratures.jl b/src/CellData/CellQuadratures.jl index afb85e818..39044a609 100644 --- a/src/CellData/CellQuadratures.jl +++ b/src/CellData/CellQuadratures.jl @@ -95,12 +95,12 @@ function CellQuadrature(trian::AppendedTriangulation,quad::Quadrature; integration_domain_style=integration_domain_style) end -@deprecate( +@deprecate( CellQuadrature(trian::Triangulation,degree,ids::DomainStyle), CellQuadrature(trian::Triangulation,degree;data_domain_style=ReferenceDomain(),integration_domain_style=ids) ) -@deprecate( +@deprecate( CellQuadrature(trian::AppendedTriangulation,degree1,degree2,ids::DomainStyle), CellQuadrature(trian::AppendedTriangulation,degree1,degree2;data_domain_style=ReferenceDomain(),integration_domain_style=ids) ) @@ -169,6 +169,16 @@ function integrate(f::CellField,quad::CellQuadrature) end end +""" + integrate(integrand, dΩ::Measure) + integrate(integrand, dΩ::CellQuadrature) + (integrand::Integrand) * dΩ + ∫(quantity) * dΩ + ∫(quantity)dΩ + +High level integral definition API, the `integrand` can be created using +[`∫`](@ref Integrand). +""" function integrate(a,quad::CellQuadrature) b = CellField(a,quad.trian,quad.data_domain_style) integrate(b,quad) @@ -176,6 +186,12 @@ end # Some syntactic sugar +""" + struct Integrand object end + ∫(object) + +Generic placeholder for a quantity `object` to be integrated against a [`CellQuadrature`](@ref). +""" struct Integrand object end @@ -187,6 +203,13 @@ const ∫ = Integrand # Cell measure +""" + get_cell_measure(trian) + get_cell_measure(strian, ttrian) + +where all arguments are [`Triangulation`](@ref)s, returns a vector containing +the volume of each cell of [`t`]`trian`. +""" function get_cell_measure(trian::Triangulation) quad = CellQuadrature(trian,0) cell_to_dV = integrate(1,quad) diff --git a/src/CellData/CellStates.jl b/src/CellData/CellStates.jl index 3fa59aec3..24c45622f 100644 --- a/src/CellData/CellStates.jl +++ b/src/CellData/CellStates.jl @@ -78,6 +78,9 @@ function CellState(v::Number,a) CellState(v,points) end +""" + update_state!(updater::Function, f::CellField...) +""" function update_state!(updater::Function,f::CellField...) ids = findall(map(i->isa(i,CellState),f)) @assert length(ids) > 0 """\n diff --git a/src/CellData/DiracDeltas.jl b/src/CellData/DiracDeltas.jl index 8ba1f76e1..2ce3056f1 100644 --- a/src/CellData/DiracDeltas.jl +++ b/src/CellData/DiracDeltas.jl @@ -7,8 +7,23 @@ struct GenericDiracDelta{D,Dt,S<:DiracDeltaSupportType} <: GridapType dΓ::Measure end +""" + const DiracDelta{D} = GenericDiracDelta{D,D,IsGridEntity} +""" const DiracDelta{D} = GenericDiracDelta{D,D,IsGridEntity} +""" + DiracDelta{D}(model::DiscreteModel, degree; tags) + DiracDelta{0}(model::DiscreteModel; tags) + DiracDelta{D}(model::DiscreteModel, labeling::FaceLabeling, degree; tags) + DiracDelta{0}(model::DiscreteModel, labeling::FaceLabeling; tags) + DiracDelta{D}(model::DiscreteModel, face_to_bgface::AbstractVector{<:Integer}, degree) + DiracDelta{D}(model::DiscreteModel, bgface_to_mask::AbstractVector{Bool}, degree) + DiracDelta( model::DiscreteModel{D}, p::Point{D,T}) + DiracDelta( model::DiscreteModel{D}, pvec::Vector{Point{D,T}}) + +where `degree` isa `Integer`. +""" function DiracDelta{D}( model::DiscreteModel, face_to_bgface::AbstractVector{<:Integer}, diff --git a/src/CellData/DomainContributions.jl b/src/CellData/DomainContributions.jl index 729efd0a0..acec53c92 100644 --- a/src/CellData/DomainContributions.jl +++ b/src/CellData/DomainContributions.jl @@ -1,5 +1,8 @@ """ + struct DomainContribution <: GridapType + +Struct to gather contributions from one or several domain(s) ([`Triangulation`](@ref)s). """ struct DomainContribution <: GridapType dict::OrderedDict{Triangulation,AbstractArray} # ordered so that iteration is deterministic (#1002) @@ -7,12 +10,23 @@ end DomainContribution() = DomainContribution(OrderedDict{Triangulation,AbstractArray}()) +""" + num_domains(a::DomainContribution) +""" num_domains(a::DomainContribution) = length(a.dict) +""" + get_domains(a::DomainContribution) +""" get_domains(a::DomainContribution) = keys(a.dict) Base.isempty(a::DomainContribution) = iszero(length(a.dict)) +""" + get_contribution(a::DomainContribution, trian::Triangulation) + +Returns the array of contributions on `trian` in `a`. +""" function get_contribution(a::DomainContribution,trian::Triangulation) if haskey(a.dict,trian) return a.dict[trian] @@ -25,6 +39,9 @@ end Base.getindex(a::DomainContribution,trian::Triangulation) = get_contribution(a,trian) +""" + add_contribution!(a::DomainContribution, trian::Triangulation, b::AbstractArray, op=+) +""" function add_contribution!(a::DomainContribution,trian::Triangulation,b::AbstractArray,op=+) S = eltype(b) @@ -129,6 +146,11 @@ function get_array(a::DomainContribution) a.dict[first(keys(a.dict))] end +""" + abstract type Measure <: GridapType + +For measures to integrate against, see [`integrate`](@ref). +""" abstract type Measure <: GridapType end function integrate(f,b::Measure) @@ -174,7 +196,7 @@ end quad :: CellQuadrature end - Measure such that the integration and target triangulations are different. + Measure such that the integration and target triangulations are different. - ttrian: Target triangulation, where the domain contribution lives. - itrian: Integration triangulation, where the integration takes place. diff --git a/src/CellData/Interpolation.jl b/src/CellData/Interpolation.jl index dc8761a49..c3479245a 100644 --- a/src/CellData/Interpolation.jl +++ b/src/CellData/Interpolation.jl @@ -1,8 +1,9 @@ -# This file implements code to evaluate CellFields on arbitrary points, based +# This file implements code to evaluate CellFields on arbitrary points, based # on tree searches. -# Interpolable struct +""" +""" struct KDTreeSearch{T} num_nearest_vertices::Int tol::T @@ -12,10 +13,16 @@ struct KDTreeSearch{T} end end +""" + struct Interpolable{M,A} <: Function +""" struct Interpolable{M,A} <: Function uh::A tol::Float64 searchmethod::M + @doc """ + Interpolable(uh; tol=1e-6, searchmethod=KDTreeSearch(; tol=tol)) + """ function Interpolable(uh; tol=1e-10, searchmethod=KDTreeSearch(; tol=tol)) new{typeof(searchmethod),typeof(uh)}(uh, tol,searchmethod) end @@ -70,6 +77,10 @@ function evaluate!(cache,f::CellField,point_to_x::AbstractVector{<:Point}) return collect(point_to_fx) # Collect into a plain array end +""" + compute_cell_points_from_vector_of_points(xs::AbstractVector{<:Point}, + trian::Triangulation, domain_style::PhysicalDomain) +""" function compute_cell_points_from_vector_of_points( xs::AbstractVector{<:Point}, trian::Triangulation, domain_style::PhysicalDomain ) @@ -183,6 +194,9 @@ function distance(polytope::ExtrusionPolytope, inv_cmap::Field, x::Point) end end +""" + make_inverse_table(i2j::AbstractVector{<:Integer}, nj::Int) +""" function make_inverse_table(i2j::AbstractVector{<:Integer},nj::Int) ni = length(i2j) @assert nj≥0 diff --git a/src/Exports.jl b/src/Exports.jl index c13f44e14..cb7f13a36 100644 --- a/src/Exports.jl +++ b/src/Exports.jl @@ -96,8 +96,12 @@ using Gridap.TensorValues: ⊗; export ⊗ @publish ReferenceFEs raviart_thomas @publish ReferenceFEs bdm @publish ReferenceFEs nedelec +@publish ReferenceFEs nedelec1 +@publish ReferenceFEs nedelec2 @publish ReferenceFEs modalC0 @publish ReferenceFEs bubble +@publish ReferenceFEs modal_lagrangian +@publish ReferenceFEs modal_serendipity @publish Geometry get_triangulation @publish Geometry num_cells @@ -206,68 +210,5 @@ using Gridap.CellData: ∫; export ∫ @publish ODEs TransientQuasilinearFEOperator @publish ODEs TransientLinearFEOperator -# Deprecated / removed - -export apply -function apply(args...) - Helpers.@unreachable """\n - Function apply has been removed and replaced by lazy_map. - This error message will be deleted in future versions. - """ -end - -export cell_measure -function cell_measure(args...) - Helpers.@unreachable """\n - Function cell_measure(a,b) has been removed and replaced by get_cell_measure(a). - This error message will be deleted in future versions. - """ -end - -export FETerm -function FETerm(args...) - Helpers.@unreachable """\n - Function FETerm has been removed. The API for specifying the weak form has changed significantly. - See the gridap/Tutorials repo for some examples of how to use the new API. - This error message will be deleted in future versions. - """ -end - -export FEEnergy -function FEEnergy(args...) - Helpers.@unreachable """\n - Function FEEnergy has been removed. The API for specifying the weak form has changed significantly. - See the gridap/Tutorials repo for some examples of how to use the new API. - This error message will be deleted in future versions. - """ -end - -export AffineFETerm -function AffineFETerm(args...) - Helpers.@unreachable """\n - Function AffineFETerm has been removed. The API for specifying the weak form has changed significantly. - See the gridap/Tutorials repo for some examples of how to use the new API. - This error message will be deleted in future versions. - """ -end - -export LinearFETerm -function LinearFETerm(args...) - Helpers.@unreachable """\n - Function LinearFETerm has been removed. The API for specifying the weak form has changed significantly. - See the gridap/Tutorials repo for some examples of how to use the new API. - This error message will be deleted in future versions. - """ -end - -export FESource -function FESource(args...) - Helpers.@unreachable """\n - Function FESource has been removed. The API for specifying the weak form has changed significantly. - See the gridap/Tutorials repo for some examples of how to use the new API. - This error message will be deleted in future versions. - """ -end - @publish FESpaces get_free_values @publish FESpaces get_dirichlet_values diff --git a/src/FESpaces/Assemblers.jl b/src/FESpaces/Assemblers.jl index 41fd4488e..7e0d7f41c 100644 --- a/src/FESpaces/Assemblers.jl +++ b/src/FESpaces/Assemblers.jl @@ -1,5 +1,6 @@ """ + abstract type AssemblyStrategy """ abstract type AssemblyStrategy end @@ -113,6 +114,9 @@ function Arrays.evaluate!(cache,k::AssemblyStrategyMap,ids::ArrayBlock,args...) a end +""" + struct DefaultAssemblyStrategy <: AssemblyStrategy +""" struct DefaultAssemblyStrategy <: AssemblyStrategy end row_map(a::DefaultAssemblyStrategy,row) = row @@ -127,6 +131,9 @@ map_cell_rows(strategy::DefaultAssemblyStrategy,cell_ids) = cell_ids map_cell_cols(strategy::DefaultAssemblyStrategy,cell_ids) = cell_ids +""" + struct GenericAssemblyStrategy{A,B,C,D} <: AssemblyStrategy +""" struct GenericAssemblyStrategy{A,B,C,D} <: AssemblyStrategy row_map::A col_map::B @@ -143,6 +150,7 @@ row_mask(a::GenericAssemblyStrategy,row) = a.row_mask(row) col_mask(a::GenericAssemblyStrategy,col) = a.col_mask(col) """ + abstract type Assembler <: GridapType """ abstract type Assembler <: GridapType end @@ -218,6 +226,8 @@ function assemble_matrix_and_vector!(A,b,a::Assembler,data) @abstractmethod end +""" +""" function assemble_matrix_and_vector_add!(A,b,a::Assembler,data) @abstractmethod end @@ -391,19 +401,27 @@ end # Abstract interface for computing the data to be sent to the assembler +""" +""" function collect_cell_matrix(trial::FESpace,test::FESpace,mat_contributions) @abstractmethod end +""" +""" function collect_cell_vector(test::FESpace,vec_contributions) @abstractmethod end +""" +""" function collect_cell_matrix_and_vector( trial::FESpace,test::FESpace,mat_contributions,vec_contributions) @abstractmethod end +""" +""" function collect_cell_matrix_and_vector( trial::FESpace,test::FESpace,mat_contributions,vec_contributions,uhd::FEFunction) @abstractmethod diff --git a/src/FESpaces/CLagrangianFESpaces.jl b/src/FESpaces/CLagrangianFESpaces.jl index daf332c28..a068e9622 100644 --- a/src/FESpaces/CLagrangianFESpaces.jl +++ b/src/FESpaces/CLagrangianFESpaces.jl @@ -97,13 +97,12 @@ function _use_clagrangian(trian::Triangulation,cell_reffe,conf::H1Conformity) end reffe1 = first(ctype_reffe1) reffe2 = first(ctype_reffe2) - if get_orders(reffe1) != get_orders(reffe2) - return false - end - if get_order(reffe1) != 1 # This can be relaxed in the future - return false - end - return true + !(reffe1 isa GenericLagrangianRefFE && reffe2 isa GenericLagrangianRefFE) && return false + get_orders(reffe1) != get_orders(reffe2) && return false + # This can be relaxed in the future + get_order(reffe1) != 1 && return false + + true end function _unsafe_clagrangian( diff --git a/src/FESpaces/ConformingFESpaces.jl b/src/FESpaces/ConformingFESpaces.jl index b44925a38..128186831 100644 --- a/src/FESpaces/ConformingFESpaces.jl +++ b/src/FESpaces/ConformingFESpaces.jl @@ -147,15 +147,17 @@ function CellFE( end function get_cell_dof_basis(model::DiscreteModel, - cell_reffe::AbstractArray{<:ReferenceFE}, + cell_reffe::AbstractArray, ::Conformity) - lazy_map(get_dof_basis,cell_reffe) + @abstractmethod + #lazy_map(get_dof_basis,cell_reffe) end function get_cell_shapefuns(model::DiscreteModel, - cell_reffe::AbstractArray{<:ReferenceFE}, + cell_reffe::AbstractArray, ::Conformity) - lazy_map(get_shapefuns,cell_reffe) + @abstractmethod + #lazy_map(get_shapefuns,cell_reffe) end # Low level conforming FE Space constructor @@ -175,7 +177,7 @@ function _ConformingFESpace( cell_dofs_ids, nfree, ndirichlet, dirichlet_dof_tag, dirichlet_cells = compute_conforming_cell_dofs( cell_fe,CellConformity(cell_fe),grid_topology,face_labeling,dirichlet_tags,dirichlet_components ) - + cell_shapefuns, cell_dof_basis = compute_cell_space(cell_fe,trian) cell_is_dirichlet = fill(false,num_cells(trian)) @@ -216,6 +218,8 @@ function compute_cell_space(cellfe::CellFE,trian::Triangulation) ) end +""" +""" function compute_cell_space(cell_fe,trian::Triangulation) cell_shapefuns, cell_dof_basis, d1, d2 = _compute_cell_space(cell_fe) SingleFieldFEBasis(cell_shapefuns,trian,TestBasis(),d1), CellDof(cell_dof_basis,trian,d2) diff --git a/src/FESpaces/ConstantFESpaces.jl b/src/FESpaces/ConstantFESpaces.jl index 71a576508..28560442f 100644 --- a/src/FESpaces/ConstantFESpaces.jl +++ b/src/FESpaces/ConstantFESpaces.jl @@ -7,7 +7,7 @@ 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 +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. """ @@ -62,7 +62,7 @@ function ConstantFESpace( @assert num_cells(model) == num_cells(trian) ncells = num_cells(trian) - prebasis = Polynomials.MonomialBasis{Dc}(T, 0) + prebasis = Polynomials.MonomialBasis(Val(Dc), T, 0) cell_basis = SingleFieldFEBasis( Fill(prebasis, ncells), trian, TestBasis(), ReferenceDomain() ) @@ -142,7 +142,7 @@ end MultiConstantFESpace(model::DiscreteModel,tags::Vector,D::Integer) MultiConstantFESpace(trians::Vector{<:BoundaryTriangulation{Df}}) -Extension of `ConstantFESpace`, representing a FESpace which is constant on each +Extension of `ConstantFESpace`, representing a FESpace which is constant on each of it's N triangulations. """ struct MultiConstantFESpace{N,T,V} <: SingleFieldFESpace diff --git a/src/FESpaces/DiscreteModelWithFEMaps.jl b/src/FESpaces/DiscreteModelWithFEMaps.jl index 4d4be7a05..fd8b787c1 100644 --- a/src/FESpaces/DiscreteModelWithFEMaps.jl +++ b/src/FESpaces/DiscreteModelWithFEMaps.jl @@ -90,8 +90,9 @@ function _compute_node_coordinates(grid,xh) end +""" +""" function add_mesh_displacement!(grid::GridWithFEMap,dh::FEFunction) - Xh = grid.fe_map Xh_fv = get_free_dof_values(Xh) @@ -101,11 +102,11 @@ function add_mesh_displacement!(grid::GridWithFEMap,dh::FEFunction) Xh_dv .= Xh_dv .+ get_dirichlet_dof_values(get_fe_space(dh)) _compute_node_coordinates(grid,Xh) - end +""" +""" function update_coordinates!(grid::GridWithFEMap,dh::FEFunction) - Xh = grid.fe_map Xh_fv = get_free_dof_values(Xh) @@ -115,7 +116,6 @@ function update_coordinates!(grid::GridWithFEMap,dh::FEFunction) Xh_dv .= get_dirichlet_dof_values(get_fe_space(dh)) _compute_node_coordinates(grid,Xh) - end # Interface @@ -138,6 +138,12 @@ get_facet_normal(grid::GridWithFEMap) = get_facet_normal(grid.grid) # end # end +""" + DiscreteModelWithFEMap(model::DiscreteModel,order; kwargs...) + DiscreteModelWithFEMap(model,orders::AbstractArray; kwargs...) + +Returns a [`MappedDiscreteModel`](@ref). +""" function DiscreteModelWithFEMap(model::DiscreteModel,order; kwargs...) mapped_grid = GridWithFEMap(model,order;kwargs...) MappedDiscreteModel(model,mapped_grid) diff --git a/src/FESpaces/DivConformingFESpaces.jl b/src/FESpaces/DivConformingFESpaces.jl deleted file mode 100644 index 5decd79ab..000000000 --- a/src/FESpaces/DivConformingFESpaces.jl +++ /dev/null @@ -1,189 +0,0 @@ -# This source file is though to put that code required in order to -# customize ConformingFESpaces.jl to H(div)-conforming global FE Spaces built -# out of RaviartThomas FEs. In particular, this customization is in the -# definition of the shape functions (get_cell_shapefuns) and the DoFs -# (get_cell_dof_basis) of the **global** FE space, which requires a sign flip -# for those sitting on facets of the slave cell of the facet. - -# Two key ingredients in the implementation of this type of ReferenceFE are the -# get_cell_shapefuns(model,cell_reffes,::Conformity) and -# get_cell_dof_basis(mode,cell_reffes,::Conformity) overloads. -# These are written such that, for each cell K, they return the shape functions -# and dof values in the *global* RT space. For a dof owned by a face which is shared by -# two cells, there is a master and a slave cell. The slave cell first computes the -# shape functions and dof values using local-to-cell data structures, but then flips the -# sign of both in order to get their corresponding counterparts in the **global** -# RT space. As as result we have the following: - -# * When we interpolate a function into the global FE space, and we perform the cell-wise -# DoF values to global DoF values gather operation, we can either extract the global DoF value -# from the master or slave cell without worrying about the sign. -# * When we evaluate a global FE function, and we perform the global DoF values to -# cell-wise DoF values scatter operation, we don't have to worry about the sign either. -# On the slave cell, we will have both the sign of the DoF value, and the sign of the -# shape function corresponding to the global DoF. -# * We do NOT have to use the signed determinant, but its absolute value, in the Piola Map. - -struct TransformRTDofBasis{Dc,Dp} <: Map end ; - -function get_cell_dof_basis(model::DiscreteModel, - cell_reffe::AbstractArray{<:GenericRefFE{<:DivConforming}}, - ::DivConformity, - sign_flip=get_sign_flip(model, cell_reffe)) - cell_map = get_cell_map(Triangulation(model)) - phi = cell_map[1] - Jt = lazy_map(Broadcasting(∇),cell_map) - x = lazy_map(get_nodes,lazy_map(get_dof_basis,cell_reffe)) - Jtx = lazy_map(evaluate,Jt,x) - reffe = cell_reffe[1] - Dc = num_dims(reffe) - # @santiagobadia: A hack here, for RT returns Float64 and for BDM VectorValue{Float64} - et = eltype(return_type(get_prebasis(reffe))) - pt = Point{Dc,et} - Dp = first(size(return_type(phi,zero(pt)))) - k = TransformRTDofBasis{Dc,Dp}() - lazy_map(k,cell_reffe,Jtx,sign_flip) -end - -function get_cell_shapefuns(model::DiscreteModel, - cell_reffe::AbstractArray{<:GenericRefFE{<:DivConforming}}, - ::DivConformity, - sign_flip=get_sign_flip(model, cell_reffe)) - cell_reffe_shapefuns=lazy_map(get_shapefuns,cell_reffe) - k=ContraVariantPiolaMap() - lazy_map(k, - cell_reffe_shapefuns, - get_cell_map(Triangulation(model)), - lazy_map(Broadcasting(constant_field), sign_flip)) -end - -struct SignFlipMap{T} <: Map - model::T -end - -function return_cache(k::SignFlipMap,reffe,cell_id) - model = k.model - D = num_cell_dims(model) - gtopo = get_grid_topology(model) - - # Extract composition among cells and facets - cell_wise_facets_ids = get_faces(gtopo, D, D - 1) - cache_cell_wise_facets_ids = array_cache(cell_wise_facets_ids) - - # Extract cells around facets - cells_around_facets = get_faces(gtopo, D - 1, D) - cache_cells_around_facets = array_cache(cells_around_facets) - - (cell_wise_facets_ids, - cache_cell_wise_facets_ids, - cells_around_facets, - cache_cells_around_facets, - CachedVector(Bool)) - -end - -function evaluate!(cache,k::SignFlipMap,reffe,cell_id) - model = k.model - - cell_wise_facets_ids, - cache_cell_wise_facets_ids, - cells_around_facets, - cache_cells_around_facets, - sign_flip_cached = cache - - setsize!(sign_flip_cached, (num_dofs(reffe),)) - sign_flip = sign_flip_cached.array - sign_flip .= false - - D = num_dims(reffe) - face_own_dofs = get_face_own_dofs(reffe) - facet_lid = get_offsets(get_polytope(reffe))[D] + 1 - cell_facets_ids = getindex!(cache_cell_wise_facets_ids, - cell_wise_facets_ids, - cell_id) - for facet_gid in cell_facets_ids - facet_cells_around = getindex!(cache_cells_around_facets, - cells_around_facets, - facet_gid) - is_slave = (findfirst((x) -> (x == cell_id), facet_cells_around) == 2) - if is_slave - for dof in face_own_dofs[facet_lid] - sign_flip[dof] = true - end - end - facet_lid = facet_lid + 1 - end - sign_flip -end - -function get_sign_flip(model::DiscreteModel, - cell_reffe::AbstractArray{<:GenericRefFE{<:DivConforming}}) - lazy_map(SignFlipMap(model), - cell_reffe, - IdentityVector(Int32(num_cells(model)))) -end - -function return_cache(::TransformRTDofBasis{Dc,Dp}, - reffe::GenericRefFE{<:DivConforming}, - Jtx, - ::AbstractVector{Bool}) where {Dc,Dp} - p = get_polytope(reffe) - prebasis = get_prebasis(reffe) - order = get_order(prebasis) - # @santiagobadia: Hack as above - et = eltype(return_type(prebasis)) - dofs = get_dof_basis(reffe) - nodes, nf_nodes, nf_moments = get_nodes(dofs), - get_face_nodes_dofs(dofs), - get_face_moments(dofs) - db = MomentBasedDofBasis(nodes,nf_moments,nf_nodes) - face_moments = [ similar(i,VectorValue{Dp,et}) for i in nf_moments ] - - cache = (db.nodes, db.face_nodes, nf_moments, face_moments) - cache -end - -function evaluate!(cache, - ::TransformRTDofBasis, - reffe::GenericRefFE{<:DivConforming}, - Jt_q, - sign_flip::AbstractVector{Bool}) - nodes, nf_nodes, nf_moments, face_moments = cache - face_own_dofs=get_face_own_dofs(reffe) - for face in 1:length(face_moments) - nf_moments_face = nf_moments[face] - face_moments_face = face_moments[face] - if length(nf_moments_face) > 0 - sign = (-1)^sign_flip[face_own_dofs[face][1]] - num_qpoints, num_moments = size(nf_moments_face) - for i in 1:num_qpoints - Jt_q_i = Jt_q[nf_nodes[face][i]] - change = sign * meas(Jt_q_i) * pinvJt(Jt_q_i) - for j in 1:num_moments - face_moments_face[i,j] = change ⋅ nf_moments_face[i,j] - end - end - end - end - MomentBasedDofBasis(nodes,face_moments,nf_nodes) -end - - -# Support for DIV operator -function DIV(f::LazyArray{<:Fill{T}}) where T - df=DIV(f.args[1]) - k=f.maps.value - lazy_map(k,df) -end -function DIV(f::LazyArray{<:Fill{Broadcasting{Operation{ContraVariantPiolaMap}}}}) - ϕrgₖ = f.args[1] - fsign_flip = f.args[4] - div_ϕrgₖ = lazy_map(Broadcasting(divergence),ϕrgₖ) - fsign_flip=lazy_map(Broadcasting(Operation(x->(-1)^x)), fsign_flip) - lazy_map(Broadcasting(Operation(*)),fsign_flip,div_ϕrgₖ) -end -function DIV(a::LazyArray{<:Fill{typeof(linear_combination)}}) - i_to_basis = DIV(a.args[2]) - i_to_values = a.args[1] - lazy_map(linear_combination,i_to_values,i_to_basis) -end diff --git a/src/FESpaces/FEAutodiff.jl b/src/FESpaces/FEAutodiff.jl index d91b1cdb1..c06b49864 100644 --- a/src/FESpaces/FEAutodiff.jl +++ b/src/FESpaces/FEAutodiff.jl @@ -52,6 +52,9 @@ function _jacobian(f,uh,fuh::DomainContribution) terms end +""" + hessian(f::Function, uh::FEFunction) +""" function hessian(f::Function,uh::FEFunction) fuh = f(uh) _hessian(f,uh,fuh) diff --git a/src/FESpaces/FEOperators.jl b/src/FESpaces/FEOperators.jl index c47a3e7c1..605e70648 100644 --- a/src/FESpaces/FEOperators.jl +++ b/src/FESpaces/FEOperators.jl @@ -9,7 +9,6 @@ A `FEOperator` contains finite element problem, that is assembled as far as possible and ready to be solved. -See also [FETerm](@ref) """ abstract type FEOperator <: GridapType end diff --git a/src/FESpaces/FESpaceFactories.jl b/src/FESpaces/FESpaceFactories.jl index b9a1d0831..74cad9f1a 100644 --- a/src/FESpaces/FESpaceFactories.jl +++ b/src/FESpaces/FESpaceFactories.jl @@ -108,9 +108,9 @@ function FESpace( end function FESpace(model::DiscreteModel, - reffe::Tuple{<:ReferenceFEName,Any,Any}; kwargs...) - basis, reffe_args,reffe_kwargs = reffe - cell_reffe = ReferenceFE(model,basis,reffe_args...;reffe_kwargs...) + reffe::Tuple{<:Union{ReferenceFEName,Symbol},Any,Any}; kwargs...) + reffe_name, reffe_args,reffe_kwargs = reffe + cell_reffe = ReferenceFE(model,reffe_name,reffe_args...;reffe_kwargs...) FESpace(model,cell_reffe;kwargs...) end diff --git a/src/FESpaces/FESpaceInterface.jl b/src/FESpaces/FESpaceInterface.jl index 028346012..076643596 100644 --- a/src/FESpaces/FESpaceInterface.jl +++ b/src/FESpaces/FESpaceInterface.jl @@ -69,21 +69,29 @@ function test_fe_function(f::FEFunction) @test length(cell_values) == num_cells(trian) end +""" + abstract type FESpace <: GridapType end + +Abstract finite element space. +""" abstract type FESpace <: GridapType end # Minimal FE interface (used by FEOperator) """ + get_free_dof_ids(f::FESpace) """ function get_free_dof_ids(f::FESpace) @abstractmethod end """ + num_free_dofs(f::FESpace) = length(get_free_dof_ids(f)) """ num_free_dofs(f::FESpace) = length(get_free_dof_ids(f)) """ + zero_free_values(f::FESpace) """ function zero_free_values(f::FESpace) V = get_vector_type(f) @@ -92,6 +100,9 @@ function zero_free_values(f::FESpace) return free_values end +""" + get_vector_type(fs::FESpace) +""" function get_vector_type(fs::FESpace) @abstractmethod end @@ -111,6 +122,9 @@ function get_triangulation(fe::FESpace) end # TODO this is quite hacky. Only needed by the zero mean space +""" + EvaluationFunction(fe::FESpace, free_values) = FEFunction(fe,free_values) +""" function EvaluationFunction(fe::FESpace, free_values) FEFunction(fe,free_values) end @@ -126,6 +140,8 @@ function get_dof_value_type(f::FESpace) get_dof_value_type(get_fe_basis(f),get_fe_dof_basis(f)) end +""" +""" function get_dof_value_type(cell_shapefuns::CellField,cell_dof_basis::CellDof) cell_dof_values = cell_dof_basis(cell_shapefuns) eltype(eltype(cell_dof_values)) @@ -165,6 +181,8 @@ function get_cell_dof_basis(f::FESpace) error(msg) end +""" +""" function get_trial_fe_basis(f::FESpace) v = get_fe_basis(f) cell_v = get_data(v) @@ -179,10 +197,20 @@ end # Basis related +""" + abstract type BasisStyle + +Trait for trial or test [`FEBasis`](@ref), the subtypes are the structs `TrialBasis` and `TestBasis`. +""" abstract type BasisStyle end struct TrialBasis <: BasisStyle end struct TestBasis <: BasisStyle end +""" + abstract type FEBasis <: CellField + +Has traits [BasisStyle](@ref) and [DomainStyle](@ref). +""" abstract type FEBasis <: CellField end BasisStyle(::Type{<:FEBasis}) = @abstractmethod BasisStyle(::T) where T <: FEBasis = BasisStyle(T) @@ -250,16 +278,36 @@ end # Constraint-related +""" + abstract type ConstraintStyle + +Trait for (un)constrained [`FESpace`](@ref)s, the subtypes are the structs +[`Constrained`](@ref) and [`UnConstrained`](@ref). +""" abstract type ConstraintStyle end +""" + struct Constrained <: ConstraintStyle +""" struct Constrained <: ConstraintStyle end +""" + struct UnConstrained <: ConstraintStyle +""" struct UnConstrained <: ConstraintStyle end ConstraintStyle(::Type{<:FESpace}) = @abstractmethod ConstraintStyle(::T) where T<:FESpace = ConstraintStyle(T) +""" + has_constraints(::Type{= 2 - prebasis = Polynomials.PCurlGradMonomialBasis{D}(T, order) + prebasis = Polynomials.FEEC_poly_basis(Val(D),T,order+1,D-1,:Q⁻,Monomial; rotate_90=(D==2)) elseif space == :ND @assert D >= 2 - prebasis = Polynomials.NedelecPrebasisOnSimplex{D}(order) + prebasis = Polynomials.NedelecPolyBasisOnSimplex{D}(Monomial, T, order) else - prebasis = Polynomials.MonomialBasis{D}(T, order, _filter_from_space(space)) + prebasis = Polynomials.MonomialBasis(Val(D), T, order, _filter_from_space(space)) end return prebasis end @@ -67,7 +67,7 @@ end function PolytopalFESpace( vector_type::Type, model::DiscreteModel, - cell_prebasis::AbstractArray; + cell_prebasis::AbstractArray; trian = Triangulation(model), labels = get_face_labeling(model), dirichlet_tags = Int[], @@ -95,7 +95,7 @@ function PolytopalFESpace( cell_shapefuns = orthogonalise_basis(cell_shapefuns, trian, order) end fe_basis = SingleFieldFEBasis(cell_shapefuns, trian, TestBasis(), domain_style) - + ncomps = num_components(return_type(first(cell_prebasis))) if !isnothing(dirichlet_masks) @check length(dirichlet_masks) == ncomps @@ -273,7 +273,7 @@ function Arrays.evaluate!(cache,::OrthogonaliseBasisMap,M::Matrix) return N end -# Orthogonalise a basis against itself, with respect +# Orthogonalise a basis against itself, with respect # of the inner product defined by M function gram_shmidt!(N,M) n = size(M,1) @@ -297,15 +297,15 @@ function gram_shmidt!(N,M) return N end -# Local kernel removal +# Local kernel removal function remove_local_kernel(cell_basis,trian,T,order,local_kernel) - if isa(local_kernel,Function) + if isa(local_kernel,Function) local_kernel_func = local_kernel - else + else local_kernel_func = _kernel_from_symbol(local_kernel,T,cell_basis) end - + cell_quads = Quadrature(trian,2*order) cell_integ = local_kernel_func(lazy_map(transpose,cell_basis)) cell_vals = lazy_map(evaluate,cell_integ,lazy_map(get_coordinates,cell_quads)) @@ -314,15 +314,6 @@ function remove_local_kernel(cell_basis,trian,T,order,local_kernel) return lazy_map(linear_combination,cell_coeffs,cell_basis) end -# Stolen from the MomentBased branch -component_basis(T::Type{<:Real}) = [one(T)] -function component_basis(V::Type{<:MultiValue}) - T = eltype(V) - n = num_components(V) - z, o = zero(T), one(T) - return [V(ntuple(i -> ifelse(i == j, o, z),Val(n))) for j in 1:n] -end - function _kernel_from_symbol(k::Symbol,T,cell_basis) if k == :constants fields = map(constant_field,component_basis(T)) @@ -346,7 +337,7 @@ end function Arrays.evaluate!(cache,k::NullspaceMap,K::Matrix) f = svd(K;full=true) # If K is not square, there svd! doesn't really use cache - + n = size(K, 2) m = sum(s -> s > k.tol, f.S) setsize!(cache, (n, n - m)) @@ -362,23 +353,23 @@ function Arrays.evaluate!(cache,k::NullspaceMap,K::Matrix) end # struct CentroidCoordinateChangeMap <: Map end -# +# # function Arrays.lazy_map(::typeof(evaluate),a::LazyArray{<:Fill{typeof(centroid_map)}},x::AbstractVector) # polys = a.args[1] # lazy_map(CentroidCoordinateChangeMap(),polys,x) # end -# +# # function Arrays.evaluate!(cache,::CentroidCoordinateChangeMap,poly::Polytope,x::Point) # pmin, pmax = get_bounding_box(poly) # xc = 0.5 * (pmin + pmax) # h = 0.5 * (pmax - pmin) # return (x - xc) ./ h # end -# +# # function Arrays.return_cache(::CentroidCoordinateChangeMap,poly::Polytope,x::AbstractVector{<:Point}) # return CachedArray(similar(x)) # end -# +# # function Arrays.evaluate!(cache,::CentroidCoordinateChangeMap,poly::Polytope,x::AbstractVector{<:Point}) # setsize!(cache,size(x)) # y = cache.array @@ -396,7 +387,7 @@ end function shoelace(face_ents) shift = circshift(face_ents, -1) area_components = map(face_ents, shift) do x1, x2 - x1[1] * x2[2] - x2[1] * x1[2] + x1[1] * x2[2] - x2[1] * x1[2] end area = 0.5 * abs(sum(area_components)) return area @@ -407,15 +398,15 @@ end # if D == 3 # @notimplemented # elseif isa(p, ExtrusionPolytope{2}) -# if p == QUAD +# if p == QUAD # perm = [1,2,4,3] # elseif p == TRI # perm = [1,2,3] # end -# elseif isa(p, Polygon) +# elseif isa(p, Polygon) # perm = collect(1:length(p.edge_vertex_graph)) # end -# +# # dim = get_dimranges(p)[face+1] # face_ents = get_face_coordinates(p)[dim] # if face == 0 @@ -436,11 +427,11 @@ end # end # function get_facet_centroid(p::Polytope{D}, face::Int) where D -# +# # if D == 3 # @notimplemented # end -# +# # dim = get_dimranges(p)[face+1] # face_coords = get_face_coordinates(p)[dim] # if isa(p, ExtrusionPolytope{2}) || isa(p, ExtrusionPolytope{1}) @@ -454,17 +445,17 @@ end # elseif face == 2 # ents = map(Reindex(face_coords...),perm) # shift = circshift(ents, -1) -# +# # components_x = map(ents, shift) do x1, x2 # ( x1[1] + x2[1] ) * ( x1[1] * x2[2] - x2[1] * x1[2] ) # end # components_y = map(ents, shift) do x1, x2 # ( x1[2] + x2[2] ) * ( x1[1] * x2[2] - x2[1] * x1[2] ) # end -# +# # area = get_facet_measure(p, face) # centroid_x = (1 ./ (6*area)) * sum(components_x) -# centroid_y = (1 ./ (6*area)) * sum(components_y) +# centroid_y = (1 ./ (6*area)) * sum(components_y) # centroid = VectorValue{2, Float64}(centroid_x..., centroid_y...) # end # end @@ -482,7 +473,7 @@ function get_facet_diameter(p::Polytope{D}, face::Int) where D norm(x[1]-x[2]) end elseif face == 2 - h = 0.0 + h = 0.0 n_sides = length(X...) for i in 1:(n_sides-1) for j in (i+1):n_sides @@ -493,7 +484,7 @@ function get_facet_diameter(p::Polytope{D}, face::Int) where D return h end -################## +################## function FESpaces.renumber_free_and_dirichlet_dof_ids( space::FESpaces.PolytopalFESpace,free_dof_ids,dir_dof_ids @@ -528,7 +519,7 @@ end function get_cell_conformity(space::PolytopalFESpace) trian = get_triangulation(space) - + monomial_conformity = only(space.metadata) ndofs = length(monomial_conformity.dof_to_term) D = length(monomial_conformity.orders) diff --git a/src/FESpaces/Pullbacks.jl b/src/FESpaces/Pullbacks.jl new file mode 100644 index 000000000..4e55977c2 --- /dev/null +++ b/src/FESpaces/Pullbacks.jl @@ -0,0 +1,162 @@ + +function get_cell_dof_basis( + model::DiscreteModel, cell_reffe::AbstractArray{T}, conf::Conformity +) where T <: ReferenceFE + + pushforward = Pushforward(get_name(T), conf) + if pushforward isa IdentityPiolaMap + lazy_map(get_dof_basis,cell_reffe) + else + pushforward, cell_change, cell_args = get_cell_pushforward( + pushforward, model, cell_reffe) + cell_ref_dofs = lazy_map(get_dof_basis, cell_reffe) + cell_phy_dofs = lazy_map(inverse_map(Pullback(pushforward)), cell_ref_dofs, cell_args...) + lazy_map(linear_combination, cell_change, cell_phy_dofs) # TODO: Inverse and transpose + end +end + +function get_cell_shapefuns( + model::DiscreteModel, cell_reffe::AbstractArray{T}, conf::Conformity +) where T <: ReferenceFE + + pushforward = Pushforward(get_name(T), conf) + if pushforward isa IdentityPiolaMap + lazy_map(get_shapefuns,cell_reffe) + else + pushforward, cell_change, cell_args = get_cell_pushforward( + pushforward, model, cell_reffe) + cell_ref_fields = lazy_map(get_shapefuns, cell_reffe) + cell_phy_fields = lazy_map(pushforward, cell_ref_fields, cell_args...) + lazy_map(linear_combination, cell_change, cell_phy_fields) + end +end + +function get_cell_pushforward( + ::Pushforward, model::DiscreteModel, cell_reffe +) + @abstractmethod +end + +# ContraVariantPiolaMap + +function get_cell_pushforward( + p::ContraVariantPiolaMap, model::DiscreteModel, cell_reffe +) + cell_map = get_cell_map(get_grid(model)) + Jt = lazy_map(Broadcasting(∇),cell_map) + change = get_sign_flip(model, cell_reffe) + return p, change, (Jt,) +end + +# CoVariantPiolaMap + +function get_cell_pushforward( + p::CoVariantPiolaMap, model::DiscreteModel, cell_reffe +) + cell_map = get_cell_map(get_grid(model)) + Jt = lazy_map(Broadcasting(∇),cell_map) + change = lazy_map(r -> Diagonal(ones(num_dofs(r))), cell_reffe) # TODO: Replace by edge-signs + return p, change, (Jt,) +end + +# DoubleContraVariantPiolaMap + +using Gridap.ReferenceFEs: DoubleContraVariantPiolaMap +function get_cell_pushforward( + ::DoubleContraVariantPiolaMap, model::DiscreteModel, cell_reffe, conformity +) + cell_map = get_cell_map(get_grid(model)) + Jt = lazy_map(Broadcasting(∇),cell_map) + change = lazy_map(r -> Diagonal(fill(one(Float64), num_dofs(r))), cell_reffe) + #change = get_sign_flip(model, cell_reffe) + return DoubleContraVariantPiolaMap(), change, (Jt,) +end + +# NormalSignMap + +""" + struct NormalSignMap <: Map + ... + end + +The `NormalSignMap` compute the signs to apply to the mapped reference normals, +for each facet of a physical cell. + +Each physical facet ``f`` is shared by up to two cells ``K`` and ``K'``. The +orientation of the physical/global normal to ``f`` is chosen by the main cell +``K``, the first one in the list of adjascent cells to ``f`` in the grid topology. + +The physical/global normal is the (normalized) Piola mapped reference normal to +``f̂ = F⁻¹(f)`` where ``F`` is the geometrical map ``F:K̂->K``. It is also minus +the (normalized) Piola mapped reference normal to ``f̂' = F'⁻¹(f)`` where ``F'`` +is the geometrical map ``F':K̂->K'``. +""" +struct NormalSignMap{T} <: Map + model::T + facet_owners::Vector{Int32} +end + +function NormalSignMap(model) + facet_owners = compute_facet_owners(model) + NormalSignMap(model,facet_owners) +end + +function return_value(k::NormalSignMap,reffe,facet_own_dofs,cell) + Diagonal(fill(one(Float64), num_dofs(reffe))) +end + +function return_cache(k::NormalSignMap,reffe,facet_own_dofs,cell) + model = k.model + Dc = num_cell_dims(model) + topo = get_grid_topology(model) + + cell_facets = get_faces(topo, Dc, Dc-1) + cell_facets_cache = array_cache(cell_facets) + + return cell_facets, cell_facets_cache, CachedVector(Float64) +end + +function evaluate!(cache,k::NormalSignMap,reffe,facet_own_dofs,cell) + cell_facets,cell_facets_cache,dof_sign_cache = cache + facet_owners = k.facet_owners + + setsize!(dof_sign_cache, (num_dofs(reffe),)) + dof_sign = dof_sign_cache.array + + o = one(eltype(dof_sign)) + fill!(dof_sign, o) + + facets = getindex!(cell_facets_cache,cell_facets,cell) + for (lfacet,facet) in enumerate(facets) + owner = facet_owners[facet] + if owner != cell + for dof in facet_own_dofs[lfacet] + dof_sign[dof] = -o + end + end + end + + return Diagonal(dof_sign) +end + +function get_sign_flip(model::DiscreteModel{Dc}, cell_reffe) where Dc + # Comment: lazy_maps on cell_reffes are very optimised, since they are CompressedArray/FillArray + get_facet_own_dofs(reffe) = view(get_face_own_dofs(reffe),get_dimrange(get_polytope(reffe),Dc-1)) + cell_facet_own_dofs = lazy_map(get_facet_own_dofs, cell_reffe) + cell_ids = IdentityVector(Int32(num_cells(model))) + return lazy_map(NormalSignMap(model), cell_reffe, cell_facet_own_dofs, cell_ids) +end + +function compute_facet_owners(model::DiscreteModel{Dc}) where {Dc} + topo = get_grid_topology(model) + facet_to_cell = get_faces(topo, Dc-1, Dc) + + nfacets = num_faces(topo, Dc-1) + owners = Vector{Int32}(undef, nfacets) + for facet in 1:nfacets + facet_cells = view(facet_to_cell, facet) + owners[facet] = first(facet_cells) + end + + return owners +end diff --git a/src/FESpaces/SingleFieldFESpaces.jl b/src/FESpaces/SingleFieldFESpaces.jl index ccb292859..79b90205b 100644 --- a/src/FESpaces/SingleFieldFESpaces.jl +++ b/src/FESpaces/SingleFieldFESpaces.jl @@ -137,6 +137,9 @@ function CellField(fs::SingleFieldFESpace,cell_vals) GenericCellField(cell_field,get_triangulation(v),DomainStyle(v)) end +""" + struct SingleFieldFEFunction{T<:CellField} <: FEFunction +""" struct SingleFieldFEFunction{T<:CellField} <: FEFunction cell_field::T cell_dof_values::AbstractArray{<:AbstractVector{<:Number}} @@ -159,6 +162,7 @@ get_fe_space(f::SingleFieldFEFunction) = f.fe_space The resulting FEFunction will be in the space if and only if `dirichlet_values` are the ones provided by `get_dirichlet_dof_values(fs)` +Return a [`SingleFieldFEFunction`](@ref). """ function FEFunction( fs::SingleFieldFESpace, free_values::AbstractVector, dirichlet_values::AbstractVector) @@ -239,6 +243,8 @@ function compute_dirichlet_values_for_tags(f::SingleFieldFESpace,tag_to_object) compute_dirichlet_values_for_tags!(dirichlet_values,dirichlet_values_scratch,f,tag_to_object) end +""" +""" function compute_dirichlet_values_for_tags!( dirichlet_values, dirichlet_values_scratch, diff --git a/src/FESpaces/SparseMatrixAssemblers.jl b/src/FESpaces/SparseMatrixAssemblers.jl index cdecae339..ee0b05503 100644 --- a/src/FESpaces/SparseMatrixAssemblers.jl +++ b/src/FESpaces/SparseMatrixAssemblers.jl @@ -1,4 +1,5 @@ """ + abstract type SparseMatrixAssembler <: Assembler """ abstract type SparseMatrixAssembler <: Assembler end @@ -14,6 +15,8 @@ function get_vector_builder(a::SparseMatrixAssembler) @abstractmethod end +""" +""" get_matrix_type(a::SparseMatrixAssembler) = get_array_type(get_matrix_builder(a)) get_vector_type(a::SparseMatrixAssembler) = get_array_type(get_vector_builder(a)) @@ -102,12 +105,17 @@ function assemble_matrix_and_vector(a::SparseMatrixAssembler, data) create_from_nz(m2,v2) end +""" +""" function test_sparse_matrix_assembler(a::SparseMatrixAssembler,matdata,vecdata,data) test_assembler(a,matdata,vecdata,data) _ = get_matrix_builder(a) _ = get_vector_builder(a) end +""" + struct GenericSparseMatrixAssembler <: SparseMatrixAssembler +""" struct GenericSparseMatrixAssembler <: SparseMatrixAssembler matrix_builder vector_builder @@ -140,6 +148,9 @@ function SparseMatrixAssembler(mat,trial::FESpace,test::FESpace) end """ + SparseMatrixAssembler(trial::FESpace,test::FESpace) + +Returns a [`GenericSparseMatrixAssembler`](@ref). """ function SparseMatrixAssembler(trial::FESpace,test::FESpace) T = get_dof_value_type(trial) @@ -158,6 +169,8 @@ get_vector_builder(a::GenericSparseMatrixAssembler) = a.vector_builder get_assembly_strategy(a::GenericSparseMatrixAssembler) = a.strategy +""" +""" function symbolic_loop_matrix!(A,a::SparseMatrixAssembler,matdata) get_mat(a::Tuple) = a[1] get_mat(a) = a @@ -196,6 +209,8 @@ end end end +""" +""" function numeric_loop_matrix!(A,a::SparseMatrixAssembler,matdata) strategy = get_assembly_strategy(a) for (cellmat,_cellidsrows,_cellidscols) in zip(matdata...) @@ -232,6 +247,8 @@ end end end +""" +""" function symbolic_loop_vector!(b,a::SparseMatrixAssembler,vecdata) get_vec(a::Tuple) = a[1] get_vec(a) = a @@ -265,6 +282,8 @@ end end end +""" +""" function numeric_loop_vector!(b,a::SparseMatrixAssembler,vecdata) strategy = get_assembly_strategy(a) for (cellvec, _cellids) in zip(vecdata...) @@ -296,6 +315,8 @@ end end end +""" +""" function symbolic_loop_matrix_and_vector!(A,b,a::SparseMatrixAssembler,data) if LoopStyle(A) == DoNotLoop() return A, b @@ -324,7 +345,7 @@ function symbolic_loop_matrix_and_vector!(A,b,a::SparseMatrixAssembler,data) symbolic_loop_matrix!(A,a,matdata) symbolic_loop_vector!(b,a,vecdata) A, b -end +end @noinline function _symbolic_loop_matvec!( A, b, caches, cell_rows, cell_cols, mat1, vec1, cells = eachindex(cell_rows) @@ -339,6 +360,8 @@ end end end +""" +""" function numeric_loop_matrix_and_vector!(A,b,a::SparseMatrixAssembler,data) strategy = get_assembly_strategy(a) matvecdata, matdata, vecdata = data diff --git a/src/FESpaces/TrialFESpaces.jl b/src/FESpaces/TrialFESpaces.jl index acf8eb712..94756604d 100644 --- a/src/FESpaces/TrialFESpaces.jl +++ b/src/FESpaces/TrialFESpaces.jl @@ -53,11 +53,23 @@ end # Remove Dirichlet from the given space +""" + HomogeneousTrialFESpace(U::SingleFieldFESpace) + +Return a ﷕SingleFieldFESpace` that is `U` but with Dirichlet values set to zero. +""" function HomogeneousTrialFESpace(U::SingleFieldFESpace) dirichlet_values = zero_dirichlet_values(U) TrialFESpace(dirichlet_values,U) end +""" + HomogeneousTrialFESpace!(dirichlet_values::AbstractVector,U::SingleFieldFESpace) + +Return a ﷕SingleFieldFESpace` that is `U` but with Dirichlet values set to zero. +Uses `dirichlet_values` with zeros set in place for the container of the +Dirichlet values of the returned space. +""" function HomogeneousTrialFESpace!(dirichlet_values::AbstractVector,U::SingleFieldFESpace) fill!(dirichlet_values,zero(eltype(dirichlet_values))) TrialFESpace(dirichlet_values,U) diff --git a/src/FESpaces/CurlConformingFESpaces.jl b/src/FESpaces/deprecated/CurlConformingFESpaces.jl similarity index 81% rename from src/FESpaces/CurlConformingFESpaces.jl rename to src/FESpaces/deprecated/CurlConformingFESpaces.jl index 39690f46a..70c306610 100644 --- a/src/FESpaces/CurlConformingFESpaces.jl +++ b/src/FESpaces/deprecated/CurlConformingFESpaces.jl @@ -1,14 +1,10 @@ function get_cell_dof_basis( model::DiscreteModel, - cell_reffe::AbstractArray{<:GenericRefFE{Nedelec}}, - ::CurlConformity) - cell_map = get_cell_map(Triangulation(model)) - phi = cell_map[1] - reffe = cell_reffe[1] + cell_reffe::AbstractArray{<:GenericRefFE{Nedelec{K}}}, + ::CurlConformity) where K + Dc = num_dims(reffe) - et = eltype(return_type(get_prebasis(reffe))) - pt = Point{Dc,et} Dp = first(size(return_type(phi,zero(pt)))) cell_dofs = lazy_map(get_dof_basis,cell_reffe) cell_ownids = lazy_map(get_face_own_dofs,cell_reffe) @@ -49,12 +45,12 @@ end function get_cell_shapefuns( model::DiscreteModel, - cell_reffe::AbstractArray{<:GenericRefFE{Nedelec}}, - ::CurlConformity) + cell_reffe::AbstractArray{<:GenericRefFE{Nedelec{K}}}, + ::CurlConformity) where K cell_reffe_shapefuns = lazy_map(get_shapefuns,cell_reffe) cell_map = get_cell_map(Triangulation(model)) + cell_Jt = lazy_map(Broadcasting(∇),cell_map) k = ReferenceFEs.CoVariantPiolaMap() - lazy_map(k,cell_reffe_shapefuns,cell_map) + lazy_map(k,cell_reffe_shapefuns,cell_Jt) end - diff --git a/src/FESpaces/deprecated/DivConformingFESpaces.jl b/src/FESpaces/deprecated/DivConformingFESpaces.jl new file mode 100644 index 000000000..e72724247 --- /dev/null +++ b/src/FESpaces/deprecated/DivConformingFESpaces.jl @@ -0,0 +1,190 @@ +# This source file is though to put that code required in order to +# customize ConformingFESpaces.jl to H(div)-conforming global FE Spaces built +# out of RaviartThomas FEs. In particular, this customization is in the +# definition of the shape functions (get_cell_shapefuns) and the DoFs +# (get_cell_dof_basis) of the **global** FE space, which requires a sign flip +# for those sitting on facets of the slave cell of the facet. + +# Two key ingredients in the implementation of this type of ReferenceFE are the +# get_cell_shapefuns(model,cell_reffes,::Conformity) and +# get_cell_dof_basis(mode,cell_reffes,::Conformity) overloads. +# These are written such that, for each cell K, they return the shape functions +# and dof values in the *global* RT space. For a dof owned by a face which is shared by +# two cells, there is a master and a slave cell. The slave cell first computes the +# shape functions and dof values using local-to-cell data structures, but then flips the +# sign of both in order to get their corresponding counterparts in the **global** +# RT space. As as result we have the following: + +# * When we interpolate a function into the global FE space, and we perform the cell-wise +# DoF values to global DoF values gather operation, we can either extract the global DoF value +# from the master or slave cell without worrying about the sign. +# * When we evaluate a global FE function, and we perform the global DoF values to +# cell-wise DoF values scatter operation, we don't have to worry about the sign either. +# On the slave cell, we will have both the sign of the DoF value, and the sign of the +# shape function corresponding to the global DoF. +# * We do NOT have to use the signed determinant, but its absolute value, in the Piola Map. + +struct TransformRTDofBasis{Dc,Dp} <: Map end + +function get_cell_dof_basis( + model::DiscreteModel{Dc,Dp}, + cell_reffe::AbstractArray{<:GenericRefFE{<:DivConforming}}, + ::DivConformity, + sign_flip = get_sign_flip(model, cell_reffe) +) where {Dc,Dp} + cell_map = get_cell_map(get_grid(model)) + Jt = lazy_map(Broadcasting(∇),cell_map) + x = lazy_map(get_nodes,lazy_map(get_dof_basis,cell_reffe)) + Jtx = lazy_map(evaluate,Jt,x) + k = TransformRTDofBasis{Dc,Dp}() + lazy_map(k,cell_reffe,Jtx,sign_flip) +end + +function get_cell_shapefuns( + model::DiscreteModel, + cell_reffe::AbstractArray{<:GenericRefFE{<:DivConforming}}, + ::DivConformity, + sign_flip = get_sign_flip(model, cell_reffe) +) + cell_map = get_cell_map(get_grid(model)) + cell_Jt = lazy_map(Broadcasting(∇),cell_map) + cell_shapefuns = lazy_map(get_shapefuns,cell_reffe) + k = ContraVariantPiolaMap() + lazy_map(k,cell_shapefuns,cell_Jt,lazy_map(Broadcasting(constant_field),sign_flip)) +end + +struct SignFlipMap{T} <: Map + model::T + facet_owners::Vector{Int32} +end + +function SignFlipMap(model) + facet_owners = compute_facet_owners(model) + SignFlipMap(model,facet_owners) +end + +function return_cache(k::SignFlipMap,reffe,facet_own_dofs,cell) + model = k.model + Dc = num_cell_dims(model) + topo = get_grid_topology(model) + + cell_facets = get_faces(topo, Dc, Dc-1) + cell_facets_cache = array_cache(cell_facets) + + return cell_facets,cell_facets_cache,CachedVector(Bool) +end + +function evaluate!(cache,k::SignFlipMap,reffe,facet_own_dofs,cell) + cell_facets,cell_facets_cache,sign_flip_cache = cache + facet_owners = k.facet_owners + + setsize!(sign_flip_cache, (num_dofs(reffe),)) + sign_flip = sign_flip_cache.array + sign_flip .= false + + facets = getindex!(cell_facets_cache,cell_facets,cell) + for (lfacet,facet) in enumerate(facets) + owner = facet_owners[facet] + if owner != cell + for dof in facet_own_dofs[lfacet] + sign_flip[dof] = true + end + end + end + + return sign_flip +end + +function get_sign_flip( + model::DiscreteModel{Dc}, + cell_reffe::AbstractArray{<:GenericRefFE{<:DivConforming}} +) where Dc + # Comment: lazy_maps on cell_reffes are very optimised, since they are CompressedArray/FillArray + get_facet_own_dofs(reffe) = view(get_face_own_dofs(reffe),get_dimrange(get_polytope(reffe),Dc-1)) + cell_facet_own_dofs = lazy_map(get_facet_own_dofs,cell_reffe) + cell_ids = IdentityVector(Int32(num_cells(model))) + lazy_map(SignFlipMap(model),cell_reffe,cell_facet_own_dofs,cell_ids) +end + +function compute_facet_owners(model::DiscreteModel{Dc,Dp}) where {Dc,Dp} + topo = get_grid_topology(model) + facet_to_cell = get_faces(topo, Dc-1, Dc) + + nfacets = num_faces(topo, Dc-1) + owners = Vector{Int32}(undef, nfacets) + for facet in 1:nfacets + facet_cells = view(facet_to_cell, facet) + owners[facet] = first(facet_cells) + end + + return owners +end + +function return_cache( + ::TransformRTDofBasis{Dc,Dp}, + reffe::GenericRefFE{<:DivConforming}, + Jtx, + ::AbstractVector{Bool} +) where {Dc,Dp} + # @santiagobadia: Hack as above + et = eltype(return_type(get_prebasis(reffe))) + dofs = get_dof_basis(reffe) + + nodes = get_nodes(dofs) + nf_nodes = get_face_nodes_dofs(dofs) + nf_moments = get_face_moments(dofs) + db = MomentBasedDofBasis(nodes,nf_moments,nf_nodes) + face_moments = [ similar(i,VectorValue{Dp,et}) for i in nf_moments ] + + return db.nodes, db.face_nodes, nf_moments, face_moments +end + +function evaluate!( + cache, + ::TransformRTDofBasis, + reffe::GenericRefFE{<:DivConforming}, + Jt_q, + sign_flip::AbstractVector{Bool} +) + nodes, nf_nodes, nf_moments, face_moments = cache + face_own_dofs = get_face_own_dofs(reffe) + for face in 1:length(face_moments) + nf_moments_face = nf_moments[face] + face_moments_face = face_moments[face] + if length(nf_moments_face) > 0 + sign = (-1)^sign_flip[face_own_dofs[face][1]] + num_qpoints, num_moments = size(nf_moments_face) + for i in 1:num_qpoints + Jt_q_i = Jt_q[nf_nodes[face][i]] + change = sign * meas(Jt_q_i) * pinvJt(Jt_q_i) + for j in 1:num_moments + face_moments_face[i,j] = change ⋅ nf_moments_face[i,j] + end + end + end + end + MomentBasedDofBasis(nodes,face_moments,nf_nodes) +end + + +# Support for DIV operator + +function DIV(f::LazyArray{<:Fill}) + df = DIV(f.args[1]) + k = f.maps.value + lazy_map(k,df) +end + +function DIV(f::LazyArray{<:Fill{Broadcasting{Operation{ContraVariantPiolaMap}}}}) + ϕrgₖ = f.args[1] + fsign_flip = f.args[3] + div_ϕrgₖ = lazy_map(Broadcasting(divergence),ϕrgₖ) + fsign_flip = lazy_map(Broadcasting(Operation(x->(-1)^x)), fsign_flip) + lazy_map(Broadcasting(Operation(*)),fsign_flip,div_ϕrgₖ) +end + +function DIV(a::LazyArray{<:Fill{typeof(linear_combination)}}) + i_to_basis = DIV(a.args[2]) + i_to_values = a.args[1] + lazy_map(linear_combination,i_to_values,i_to_basis) +end diff --git a/src/Fields/AffineMaps.jl b/src/Fields/AffineMaps.jl index cbd211cc1..7b4f96e88 100644 --- a/src/Fields/AffineMaps.jl +++ b/src/Fields/AffineMaps.jl @@ -1,4 +1,7 @@ +""" + struct AffineMap <: Map +""" struct AffineMap <: Map end function evaluate!(cache,::AffineMap,G::TensorValue{D1,D2},y0::Point{D2},x::Point{D1}) where {D1,D2} @@ -6,8 +9,13 @@ function evaluate!(cache,::AffineMap,G::TensorValue{D1,D2},y0::Point{D2},x::Poin end """ -A Field with this form -y = x⋅G + y0 + struct AffineField{D1,D2,T,L} <: Field + +A Field with the form: + + y = x⋅G + y0 + +with `G`::TensorValue{`D1`,`D2`,`T`,`L`} and `y0`::Point{`D2`,`T`}. """ struct AffineField{D1,D2,T,L} <: Field gradient::TensorValue{D1,D2,T,L} @@ -20,8 +28,19 @@ struct AffineField{D1,D2,T,L} <: Field end end +""" + affine_map(gradient, origin) = AffineField(gradient, origin) + +See [`AffineField`](@ref). +""" affine_map(gradient,origin) = AffineField(gradient,origin) +function Base.zero(::Type{<:AffineField{D1,D2,T}}) where {D1,D2,T} + gradient = TensorValue{D1,D2}(tfill(zero(T),Val{D1*D2}())) + origin = Point{D2,T}(tfill(zero(T),Val{D2}())) + AffineField(gradient,origin) +end + function evaluate!(cache,f::AffineField,x::Point) G = f.gradient y0 = f.origin @@ -123,12 +142,6 @@ function lazy_map( lazy_map(Broadcasting(AffineMap()),gradients,origins,x) end -function Base.zero(::Type{<:AffineField{D1,D2,T}}) where {D1,D2,T} - gradient = TensorValue{D1,D2}(tfill(zero(T),Val{D1*D2}())) - origin = Point{D2,T}(tfill(zero(T),Val{D2}())) - AffineField(gradient,origin) -end - # Constructor from a simplex given by D1+1 points function affine_map(points::NTuple{D,Point{D2,T}}) where {D,D2,T} origin, pk = first_and_tail(points) diff --git a/src/Fields/DensifyInnerMostBlockLevelMaps.jl b/src/Fields/DensifyInnerMostBlockLevelMaps.jl index 26f93ef2a..63e30d3d3 100644 --- a/src/Fields/DensifyInnerMostBlockLevelMaps.jl +++ b/src/Fields/DensifyInnerMostBlockLevelMaps.jl @@ -1,3 +1,6 @@ +""" + struct DensifyInnerMostBlockLevelMap <: Map +""" struct DensifyInnerMostBlockLevelMap <: Map end diff --git a/src/Fields/FieldArrays.jl b/src/Fields/FieldArrays.jl index a059378a0..a7c653dbe 100644 --- a/src/Fields/FieldArrays.jl +++ b/src/Fields/FieldArrays.jl @@ -75,6 +75,11 @@ function testargs(f::AbstractArray{T},x::AbstractArray{<:Point}) where T<:Field testargs(testitem(f),x) end +""" + test_field_array(f::AbstractArray{<:Field}, x, v, cmp=(==); grad=nothing, gradgrad=nothing) + +For tests. +""" function test_field_array(f::AbstractArray{<:Field}, x, v, cmp=(==); grad=nothing, gradgrad=nothing) test_map(v,f,x;cmp=cmp) if grad != nothing @@ -210,11 +215,15 @@ for op in (:∇,:∇∇) end end +""" + linear_combination(a::AbstractVector{<:Number}, b::AbstractVector{<:Field}) + linear_combination(a::AbstractMatrix{<:Number}, b::AbstractVector{<:Field}) +""" function linear_combination(a::AbstractMatrix{<:Number},b::AbstractVector{<:Field}) LinearCombinationFieldVector(a,b) end -struct LinearCombinationFieldVector{V,F} <: AbstractVector{LinearCombinationField{V,F}} +@ahe struct LinearCombinationFieldVector{V,F} <: AbstractVector{LinearCombinationField{V,F}} values::V fields::F function LinearCombinationFieldVector(values::AbstractMatrix{<:Number},fields::AbstractVector{<:Field}) @@ -393,6 +402,16 @@ function evaluate!(cache,k::LinearCombinationMap{Colon},v::AbstractMatrix,fx::Ab r end +function evaluate!(cache,k::LinearCombinationMap{Colon},v::LinearAlgebra.Diagonal,fx::AbstractVector) + @check length(fx) == size(v,1) + setsize!(cache,(size(v,2),)) + r = cache.array + @inbounds for j in eachindex(fx) + r[j] = outer(fx[j],v.diag[j]) + end + r +end + function return_cache(k::LinearCombinationMap{Colon},v::AbstractMatrix,fx::AbstractMatrix) vf = testitem(fx) vv = testitem(v) @@ -401,6 +420,7 @@ function return_cache(k::LinearCombinationMap{Colon},v::AbstractMatrix,fx::Abstr CachedArray(r) end +# r = fx * v i.e. r[p,j] = Σᵢ fx[p,i]*v[i,j] function evaluate!(cache,k::LinearCombinationMap{Colon},v::AbstractMatrix,fx::AbstractMatrix) @check size(fx,2) == size(v,1) setsize!(cache,(size(fx,1),size(v,2))) @@ -417,6 +437,18 @@ function evaluate!(cache,k::LinearCombinationMap{Colon},v::AbstractMatrix,fx::Ab r end +function evaluate!(cache,k::LinearCombinationMap{Colon},v::LinearAlgebra.Diagonal,fx::AbstractMatrix) + @check size(fx,2) == size(v,1) + setsize!(cache,(size(fx,1),size(v,2))) + r = cache.array + @inbounds for p in 1:size(fx,1) + for j in 1:size(fx,2) + r[p,j] = outer(fx[p,j],v.diag[j]) + end + end + r +end + # Optimizing transpose testitem(a::Transpose{<:Field}) = testitem(a.parent) evaluate!(cache,k::Broadcasting{typeof(∇)},a::Transpose{<:Field}) = transpose(k(a.parent)) @@ -465,6 +497,8 @@ Base.setindex!(a::TransposeFieldIndices,v,i::Integer) = (a.matrix[i] = v) # Integration """ + integrate(a::AbstractArray{<:Field},x::AbstractVector{<:Point},w::AbstractVector{<:Real}) + Integration of a given array of fields in the "physical" space """ function integrate(a::AbstractArray{<:Field},x::AbstractVector{<:Point},w::AbstractVector{<:Real}) @@ -473,6 +507,8 @@ function integrate(a::AbstractArray{<:Field},x::AbstractVector{<:Point},w::Abstr end """ + integrate(a::AbstractArray{<:Field},q::AbstractVector{<:Point},w::AbstractVector{<:Real},j::Field) + Integration of a given array of fields in the "reference" space """ function integrate(a::AbstractArray{<:Field},q::AbstractVector{<:Point},w::AbstractVector{<:Real},j::Field) @@ -553,6 +589,22 @@ for T in (:(Point),:(AbstractArray{<:Point})) evaluate!(r,bm,rs...) end + function return_cache(k::BroadcastOpFieldArray{typeof(∘)},x::$T) + f, g = k.args + cg = return_cache(g,x) + gx = evaluate!(cg,g,x) + cf = return_cache(f,gx) + return cg, cf + end + + function evaluate!(cache, k::BroadcastOpFieldArray{typeof(∘)},x::$T) + cg, cf = cache + f, g = k.args + gx = evaluate!(cg,g,x) + fgx = evaluate!(cf,f,gx) + return fgx + end + end end @@ -738,3 +790,43 @@ for op in (:*,:⋅,:⊙,:⊗) end end end + +# Optimisations to +# lazy_map(Broadcasting(constant_field),a::AbstractArray{<:AbstractArray{<:Number}}) + +struct ConstantFieldArray{T,N,A} <: AbstractArray{ConstantField{T},N} + values::A + function ConstantFieldArray(values::AbstractArray{T,N}) where {T,N} + A = typeof(values) + new{T,N,A}(values) + end +end + +Base.size(a::ConstantFieldArray) = size(a.values) +Base.axes(a::ConstantFieldArray) = axes(a.values) +Base.getindex(a::ConstantFieldArray,i::Integer) = ConstantField(a.values[i]) + +function return_value(::Broadcasting{typeof(constant_field)},values::AbstractArray{<:Number}) + ConstantFieldArray(values) +end + +function evaluate!(cache,::Broadcasting{typeof(constant_field)},values::AbstractArray{<:Number}) + ConstantFieldArray(values) +end + +function evaluate!(c,f::ConstantFieldArray,x::Point) + return f.values +end + +function return_cache(f::ConstantFieldArray{T},x::AbstractArray{<:Point}) where T + return CachedArray(zeros(T,(size(x)...,size(f)...))) +end + +function evaluate!(c,f::ConstantFieldArray{T},x::AbstractArray{<:Point}) where T + setsize!(c,(size(x)...,size(f)...)) + r = c.array + for i in eachindex(x) + r[i,:] .= f.values + end + return r +end diff --git a/src/Fields/Fields.jl b/src/Fields/Fields.jl index 43994319a..5f1922440 100644 --- a/src/Fields/Fields.jl +++ b/src/Fields/Fields.jl @@ -1,3 +1,9 @@ +""" + +$(public_names_in_md(@__MODULE__; change_link=Dict( + :∇ => "gradient", +))) +""" module Fields using Gridap.Arrays @@ -6,6 +12,7 @@ import Gridap.Arrays: inverse_map import Gridap.Arrays: get_children import Gridap.Arrays: testitem +using Gridap.Helpers using Gridap.Helpers: @abstractmethod, @notimplemented using Gridap.Helpers: @notimplementedif, @unreachable, @check using Gridap.Helpers: tfill, first_and_tail @@ -23,6 +30,7 @@ using NLsolve using Test using StaticArrays using LinearAlgebra +using AutoHashEquals: @auto_hash_equals as @ahe import LinearAlgebra: det, inv, transpose, tr, cross import LinearAlgebra: ⋅, dot diff --git a/src/Fields/FieldsInterfaces.jl b/src/Fields/FieldsInterfaces.jl index 176c04bb2..791676372 100644 --- a/src/Fields/FieldsInterfaces.jl +++ b/src/Fields/FieldsInterfaces.jl @@ -6,6 +6,26 @@ Fields are evaluated at vectors of `Point` objects. """ const Point{D,T} = VectorValue{D,T} + +# Old out of date documention +# +# Moreover, if the [`gradient(f)`](@ref) is not provided, a default implementation that uses the +# following functions will be used. +# +# - [`evaluate_gradient!(cache,f,x)`](@ref) +# - [`return_gradient_cache(f,x)`](@ref) +# +# Higher order derivatives require the implementation of +# +# - [`evaluate_hessian!(cache,f,x)`](@ref) +# - [`return_hessian_cache(f,x)`](@ref) +# +# These four methods are only designed to be called by the default implementation of [`field_gradient(f)`](@ref) and thus +# cannot be assumed that they are available for an arbitrary field. For this reason, these functions are not +# exported. The general way of evaluating a gradient of a field is to +# build the gradient with [`gradient(f)`](@ref) and evaluating the resulting object. For evaluating +# the hessian, use two times `gradient`. + """ abstract type Field <: Map @@ -32,25 +52,6 @@ A `Field` can also provide its gradient if the following function is implemented Higher derivatives can be obtained if the resulting object also implements this method. -The next paragraph is out-of-date: - -Moreover, if the [`gradient(f)`](@ref) is not provided, a default implementation that uses the -following functions will be used. - -- [`evaluate_gradient!(cache,f,x)`](@ref) -- [`return_gradient_cache(f,x)`](@ref) - -Higher order derivatives require the implementation of - -- [`evaluate_hessian!(cache,f,x)`](@ref) -- [`return_hessian_cache(f,x)`](@ref) - -These four methods are only designed to be called by the default implementation of [`field_gradient(f)`](@ref) and thus -cannot be assumed that they are available for an arbitrary field. For this reason, these functions are not -exported. The general way of evaluating a gradient of a field is to -build the gradient with [`gradient(f)`](@ref) and evaluating the resulting object. For evaluating -the hessian, use two times `gradient`. - The interface can be tested with - [`test_field`](@ref) @@ -67,8 +68,18 @@ evaluate!(c,f::Field,x::Point) = @abstractmethod # Differentiation +""" + function gradient end + +Abstract gradient, see [`Field`](@ref). +""" function gradient end const ∇ = gradient +""" + ∇∇(f) = gradient(gradient(f)) + +hessian of f +""" ∇∇(f) = gradient(gradient(f)) gradient(f,::Val{1}) = ∇(f) @@ -79,8 +90,17 @@ evaluate!(cache,::Broadcasting{typeof(∇∇)},a::Field) = ∇∇(a) lazy_map(::Broadcasting{typeof(∇)},a::AbstractArray{<:Field}) = lazy_map(∇,a) lazy_map(::Broadcasting{typeof(∇∇)},a::AbstractArray{<:Field}) = lazy_map(∇∇,a) +""" + push_∇(∇a::Field, ϕ::Field) = pinvJt(∇(ϕ))⋅∇a + +Pushforward of `∇a` by the mapping `ϕ`, or covariant Piola transform. +""" push_∇(∇a::Field,ϕ::Field) = pinvJt(∇(ϕ))⋅∇a +""" + function pinvJt(Jt::MultiValue{Tuple{D,D}}) = inv(Jt) + function pinvJt(Jt::MultiValue{Tuple{D1,D2}}) = transpose(inv(Jt⋅transpose(J))⋅Jt) +""" function pinvJt(Jt::MultiValue{Tuple{D,D}}) where D inv(Jt) end @@ -91,6 +111,8 @@ function pinvJt(Jt::MultiValue{Tuple{D1,D2}}) where {D1,D2} transpose(inv(Jt⋅J)⋅Jt) end +""" +""" function push_∇∇(∇∇a::Field,ϕ::Field) @notimplemented """\n Second order derivatives of quantities defined in the reference domain not implemented yet. @@ -246,10 +268,20 @@ gradient(z::ZeroField) = ZeroField(gradient(z.field)) # is assumed to implement the Field interface. # I think it is conceptually better to have ConstantField as struct otherwise we break the invariant # "for any object wrapped in a GenericField we can assume that it implements the Field interface" +""" + struct ConstantField{T<:Number} <: Field + +Constant field with value of type `T`. +""" struct ConstantField{T<:Number} <: Field value::T end +""" + constant_field(value) = ConstantField(value) + +See [`ConstantField`](@ref). +""" constant_field(a) = ConstantField(a) Base.zero(::Type{ConstantField{T}}) where T = ConstantField(zero(T)) @@ -526,7 +558,10 @@ evaluate!(cache,::Broadcasting{typeof(∘)},f::Field,g::Field) = f∘g # Integration """ -Integration of a given field in the "physical" space + integrate(a::Field,x::AbstractVector{<:Point},w::AbstractVector{<:Real}) + +Numerical integration of a given field in the "physical" space. `a` is the field, +`x` the quadrature points and `w` the quadrature weights. """ function integrate(a::Field,x::AbstractVector{<:Point},w::AbstractVector{<:Real}) cache = return_cache(integrate,a,x,w) @@ -534,7 +569,10 @@ function integrate(a::Field,x::AbstractVector{<:Point},w::AbstractVector{<:Real} end """ -Integration of a given field in the "reference" space + integrate(a::Field,q::AbstractVector{<:Point},w::AbstractVector{<:Real},j::Field) + +Numerical integration of a given field in the "reference" space. `j` is the +Jacobian field of the geometrical mapping . """ function integrate(a::Field,q::AbstractVector{<:Point},w::AbstractVector{<:Real},j::Field) cache = return_cache(integrate,a,q,w,j) @@ -570,6 +608,12 @@ function evaluate!(cache,::typeof(integrate),a,q,w,j) evaluate!(ck,IntegrationMap(),aq,w,jq) end +""" + struct IntegrationMap <: Map + +[`Map`](@ref) for low level [`integrate`](@ref), that is computation of +vectorized discrete quadratures. +""" struct IntegrationMap <: Map end function evaluate!(cache,k::IntegrationMap,ax::AbstractVector,w) @@ -727,12 +771,18 @@ function test_field(f::Field, x, v, cmp=(==); grad=nothing, gradgrad=nothing) true end +""" + struct VoidFieldMap <: Map +""" struct VoidFieldMap <: Map isvoid::Bool end Arrays.evaluate!(cache,k::VoidFieldMap,b) = VoidField(b,k.isvoid) +""" + struct VoidField{F} <: Field +""" struct VoidField{F} <: Field field::F isvoid::Bool @@ -781,12 +831,18 @@ function lazy_map(::typeof(evaluate),a::LazyArray{<:Fill{VoidFieldMap}},x::Abstr lazy_map(evaluate,a.args[1],x) end +""" + struct VoidBasisMap <: Map +""" struct VoidBasisMap <: Map isvoid::Bool end Arrays.evaluate!(cache,k::VoidBasisMap,b) = VoidBasis(b,k.isvoid) +""" + struct VoidBasis{T,N,A} <: AbstractArray{T,N} +""" struct VoidBasis{T,N,A} <: AbstractArray{T,N} basis::A isvoid::Bool diff --git a/src/Fields/InverseFields.jl b/src/Fields/InverseFields.jl index 2248eda8a..f760f3dc6 100644 --- a/src/Fields/InverseFields.jl +++ b/src/Fields/InverseFields.jl @@ -8,8 +8,8 @@ inverse_map(a::Field) = InverseField(a) inverse_map(a::InverseField) = a.original function return_cache(a::InverseField,x::Point) - y₀ = [zero(x)...] # initial guess and solution - F₀ = [zero(x)...] # error + y₀ = [zero(x)...] # initial guess and solution + F₀ = [zero(x)...] # error return return_cache(a.original,x), return_cache(∇(a.original),x), y₀, F₀ end diff --git a/src/Fields/MockFields.jl b/src/Fields/MockFields.jl index e6432abf4..e8f61ae9d 100644 --- a/src/Fields/MockFields.jl +++ b/src/Fields/MockFields.jl @@ -1,4 +1,9 @@ +""" + struct MockField{T<:Number} <: Field + +For tests. +""" struct MockField{T<:Number} <: Field v::T end @@ -18,6 +23,11 @@ end testvalue(::Type{MockField{T}}) where T = MockField(zero(T)) # This is to emulate MonomialBasis that only implements at the level of array of fields. +""" + struct MockFieldArray{N,A} <: AbstractArray{GenericField{Nothing},N} + +For tests. +""" struct MockFieldArray{N,A} <: AbstractArray{GenericField{Nothing},N} values::A function MockFieldArray(values::AbstractArray{<:Number}) diff --git a/src/Geometry/AppendedTriangulations.jl b/src/Geometry/AppendedTriangulations.jl index 5684d800c..97aa8666c 100644 --- a/src/Geometry/AppendedTriangulations.jl +++ b/src/Geometry/AppendedTriangulations.jl @@ -151,6 +151,12 @@ end struct AppendedTriangulation{Dc,Dp,A,B} <: Triangulation{Dc,Dp} a::A b::B + + @doc """ + AppendedTriangulation(a::Triangulation{Dc,Dp}, b::Triangulation{Dc,Dp}) + + Union of two triangulations built on the same [`DiscreteModel`](@ref). + """ function AppendedTriangulation( a::Triangulation{Dc,Dp}, b::Triangulation{Dc,Dp}) where {Dc,Dp} @assert get_background_model(a) === get_background_model(b) diff --git a/src/Geometry/BoundaryTriangulations.jl b/src/Geometry/BoundaryTriangulations.jl index e5034a977..9fc614e64 100644 --- a/src/Geometry/BoundaryTriangulations.jl +++ b/src/Geometry/BoundaryTriangulations.jl @@ -125,7 +125,7 @@ struct BoundaryTriangulation{Dc,Dp,A,B} <: Triangulation{Dc,Dp} function BoundaryTriangulation( trian::BodyFittedTriangulation, glue) - + Dc = num_cell_dims(trian) Dp = num_point_dims(trian) A = typeof(trian) @@ -134,6 +134,11 @@ struct BoundaryTriangulation{Dc,Dp,A,B} <: Triangulation{Dc,Dp} end end +""" + Boundary(args...; kwargs...) + +Alias for [`BoundaryTriangulation`](@ref)(args...; kwargs...). +""" function Boundary(args...;kwargs...) BoundaryTriangulation(args...;kwargs...) end diff --git a/src/Geometry/CartesianDiscreteModels.jl b/src/Geometry/CartesianDiscreteModels.jl index df3f49216..8d1bea1c0 100644 --- a/src/Geometry/CartesianDiscreteModels.jl +++ b/src/Geometry/CartesianDiscreteModels.jl @@ -402,13 +402,13 @@ function _is_there_interior_cell_across_higher_dim_faces( end """ - _find_ncube_face_neighbor_deltas(p::ExtrusionPolytope{D}) -> Vector{CartesianIndex} + _find_ncube_face_neighbor_deltas(p::ExtrusionPolytope{D}) -> Vector{CartesianIndex} - Given an n-cube type ExtrusionPolytope{D}, returns V=Vector{CartesianIndex} with as many - entries as the number of faces in the boundary of the Polytope. For an entry face_lid - in this vector, V[face_lid] returns what has to be added to the CartesianIndex of a - cell in order to obtain the CartesianIndex of the cell neighbour of K across the face F - with local ID face_lid. +Given an n-cube type ExtrusionPolytope{D}, returns V=Vector{CartesianIndex} with as many +entries as the number of faces in the boundary of the Polytope. For an entry face_lid +in this vector, V[face_lid] returns what has to be added to the CartesianIndex of a +cell in order to obtain the CartesianIndex of the cell neighbour of K across the face F +with local ID face_lid. """ function _find_ncube_face_neighbor_deltas(p::ExtrusionPolytope{D}) where {D} nfaces = num_faces(p) diff --git a/src/Geometry/CompressedCellArrays.jl b/src/Geometry/CompressedCellArrays.jl index eda20cb67..a257cdf05 100644 --- a/src/Geometry/CompressedCellArrays.jl +++ b/src/Geometry/CompressedCellArrays.jl @@ -1,3 +1,13 @@ +""" + move_contributions(scell_to_val, strian) + move_contributions(scell_to_val, strian, ttrian) + move_contributions(scell_to_val, sglue, tglue) + move_contributions(scell_to_val, sglue, tglue, nothing) + move_contributions(scell_to_val, sglue, tglue, mcell_to_scell::AbstractArray) + +where `scell_to_val` isa AbstractArray, `s`/`ttrian` isa Triangulation, +`s`/`tglue` isa FaceToFaceGlue. +""" function move_contributions( scell_to_val::AbstractArray, strian::Triangulation) @@ -68,7 +78,7 @@ function return_cache(k::CombineContributionsMap{<:AbstractArray{<:Number}},scel end function evaluate!(cache,k::CombineContributionsMap{<:AbstractArray{<:Number}},scells) - z,c = cache + z,c = cache val = zero(z) for scell in scells val += getindex!(c,k.scell_to_val,scell) diff --git a/src/Geometry/DiscreteModelPortions.jl b/src/Geometry/DiscreteModelPortions.jl index 4a3867873..0a1652e08 100644 --- a/src/Geometry/DiscreteModelPortions.jl +++ b/src/Geometry/DiscreteModelPortions.jl @@ -17,6 +17,9 @@ get_face_to_parent_face(model::DiscreteModelPortion,d::Integer) = model.d_to_dfa get_cell_to_parent_cell(model::DiscreteModelPortion) = get_face_to_parent_face(model,num_cell_dims(model)) +""" + get_parent_model(model::DiscreteModelPortion) +""" get_parent_model(model::DiscreteModelPortion) = model.parent_model """ diff --git a/src/Geometry/DiscreteModels.jl b/src/Geometry/DiscreteModels.jl index f4ec84059..317d6d870 100644 --- a/src/Geometry/DiscreteModels.jl +++ b/src/Geometry/DiscreteModels.jl @@ -182,15 +182,13 @@ end """ - get_face_own_nodes(g::DiscreteModel,d::Integer) + get_face_own_nodes(g::DiscreteModel) + get_face_own_nodes(g::DiscreteModel, d::Integer) """ function get_face_own_nodes(g::DiscreteModel,d::Integer) compute_face_own_nodes(g,d) end -""" - get_face_own_nodes(g::DiscreteModel) -""" function get_face_own_nodes(g::DiscreteModel) compute_face_own_nodes(g) end @@ -373,13 +371,24 @@ function Grid(::Type{ReferenceFE{d}},model::DiscreteModel{d}) where d end """ - simplexify(model::DiscreteModel) + simplexify(model::DiscreteModel; kwargs...) """ -function simplexify(model::DiscreteModel;kwargs...) +function simplexify(model::DiscreteModel; kwargs...) umodel = UnstructuredDiscreteModel(model) simplexify(umodel;kwargs...) end +""" + ReferenceFE(model::DiscreteModel, args...; kwargs...) -> cell_to_reffe + +Return a vector containing the [`ReferenceFE`](@ref) specified by `args` and +`kwargs` for each type of cell of `model` (given by [`get_cell_type(model)`](@ref)). + +The `args` and `kwargs` are all arguments accepted by +[`ReferenceFE(::ReferenceFEName, ...; ...)`](@ref +ReferenceFE(::ReferenceFEName,a...;k...)) or [`ReferenceFE(F::Symbol, ...; ...)`](@ref +ReferenceFE(::Symbol,a...;k...)), first argument included. +""" function ReferenceFE(model::DiscreteModel,args...;kwargs...) ctype_to_polytope = get_polytopes(model) cell_to_ctype = get_cell_type(model) diff --git a/src/Geometry/Geometry.jl b/src/Geometry/Geometry.jl index fd7c36acd..63a36b8a6 100644 --- a/src/Geometry/Geometry.jl +++ b/src/Geometry/Geometry.jl @@ -1,7 +1,11 @@ """ -Exported names are -$(EXPORTS) +$(public_names_in_md(@__MODULE__; change_link=Dict( + :Irregular => "RegularityStyle", + :Regular => "RegularityStyle", + :Oriented => "OrientationStyle ", + :NonOriented => "OrientationStyle " +))) """ module Geometry diff --git a/src/Geometry/GridMocks.jl b/src/Geometry/GridMocks.jl index d2f60afeb..7ee39c1ad 100644 --- a/src/Geometry/GridMocks.jl +++ b/src/Geometry/GridMocks.jl @@ -23,6 +23,11 @@ # 1 ---1--- 2 ---5--- 3 # +""" + struct GridMock <: Grid{2,2} + +For tests. +""" struct GridMock <: Grid{2,2} end function get_node_coordinates(::GridMock) diff --git a/src/Geometry/GridTopologies.jl b/src/Geometry/GridTopologies.jl index 2a7c3c732..305df9aa5 100644 --- a/src/Geometry/GridTopologies.jl +++ b/src/Geometry/GridTopologies.jl @@ -412,10 +412,10 @@ end """ get_isboundary_face(g::GridTopology) - get_isboundary_face(g::GridTopology,d::Integer) + get_isboundary_face(g::GridTopology, d::Integer) -Returns a vector of booleans indicating if the face is a boundary face. Boundary faces -are defined in the following way: +Returns a vector of booleans indicating if the face is a boundary face. Boundary faces +are defined in the following way: - If `d = D-1`, i.e facets, a boundary facet is a facet that is adjacent to only one cell. - Otherwise, a face is a boundary face if it belongs to a boundary facet. @@ -429,6 +429,12 @@ function get_isboundary_face(g::GridTopology,d::Integer) compute_isboundary_face(g,d) end +""" + compute_isboundary_face(top::GridTopology) + compute_isboundary_face(top::GridTopology, d::Integer) + +Compute and return the result of [`get_isboundary_face`](@ref). +""" function compute_isboundary_face(g::GridTopology) D = num_cell_dims(g) d_to_dface_to_isboundary = [compute_isboundary_face(g,d) for d in 0:D] @@ -472,7 +478,7 @@ end """ get_cell_permutations(top::GridTopology) - get_cell_permutations(top::GridTopology,d::Integer) + get_cell_permutations(top::GridTopology, d::Integer) Returns an cell-wise array of permutations. For each cell, the entry contains the permutations of the local d-faces of the cell w.r.t the global d-faces of the mesh. @@ -487,6 +493,12 @@ function get_cell_permutations(top::GridTopology,d::Integer) compute_cell_permutations(top,d) end +""" + compute_cell_permutations(top::GridTopology) + compute_cell_permutations(top::GridTopology, d::Integer) + +Compute and return the result of [`get_cell_permutations`](@ref). +""" function compute_cell_permutations(top::GridTopology) D = num_cell_dims(top) tables = (compute_cell_permutations(top,d) for d in 0:D) diff --git a/src/Geometry/Grids.jl b/src/Geometry/Grids.jl index e6b4c93b3..ca8be5f9d 100644 --- a/src/Geometry/Grids.jl +++ b/src/Geometry/Grids.jl @@ -227,6 +227,9 @@ function get_cell_map(trian::Grid) lazy_map(linear_combination,cell_to_coords,cell_to_shapefuns) end +""" + get_cell_coordinates(grid::Grid) +""" function get_cell_coordinates(trian::Grid) node_to_coords = get_node_coordinates(trian) cell_to_nodes = get_cell_node_ids(trian) diff --git a/src/Geometry/MappedDiscreteModels.jl b/src/Geometry/MappedDiscreteModels.jl index 44755417a..b82250bac 100644 --- a/src/Geometry/MappedDiscreteModels.jl +++ b/src/Geometry/MappedDiscreteModels.jl @@ -19,6 +19,9 @@ end # it can be used to include a high order map implemented by any map that is # a `CellField`. # """ +""" + struct MappedGrid{Dc,Dp,T,M,L} <: Grid{Dc,Dp} +""" struct MappedGrid{Dc,Dp,T,M,L} <: Grid{Dc,Dp} grid::Grid{Dc,Dp} geo_map::T # Composition of old map and new one @@ -57,7 +60,7 @@ get_reffes(grid::MappedGrid) = get_reffes(grid.grid) get_cell_type(grid::MappedGrid) = get_cell_type(grid.grid) """ -MappedDiscreteModel + struct MappedDiscreteModel{Dc,Dp} <: DiscreteModel{Dc,Dp} Represent a model with a `MappedGrid` grid. See also [`MappedGrid`](@ref). diff --git a/src/Geometry/SkeletonTriangulations.jl b/src/Geometry/SkeletonTriangulations.jl index 936834956..570d58ea1 100644 --- a/src/Geometry/SkeletonTriangulations.jl +++ b/src/Geometry/SkeletonTriangulations.jl @@ -44,6 +44,11 @@ struct SkeletonTriangulation{Dc,Dp,B,C} <: Triangulation{Dc,Dp} end end +""" + Skeleton(args...; kwargs...) + +Alias for [`SkeletonTriangulation`](@ref)(args..., kwargs...). +""" function Skeleton(args...;kwargs...) SkeletonTriangulation(args...;kwargs...) end @@ -235,6 +240,11 @@ function InterfaceTriangulation(model::DiscreteModel,cell_to_is_in::Vector{Bool} InterfaceTriangulation(model,cell_to_inout) end +""" + Interface(args...; kwargs...) + +Alias for [`InterfaceTriangulation`](@ref)(args...; kwargs...). +""" function Interface(args...;kwargs...) InterfaceTriangulation(args...;kwargs...) end diff --git a/src/Geometry/Triangulations.jl b/src/Geometry/Triangulations.jl index d3b41c444..e2045b79c 100644 --- a/src/Geometry/Triangulations.jl +++ b/src/Geometry/Triangulations.jl @@ -1,7 +1,7 @@ """ abstract type Triangulation{Dt,Dp} -A discredited physical domain associated with a `DiscreteModel{Dm,Dp}`. +A discretized physical domain associated with a `DiscreteModel{Dm,Dp}`. `Dt` and `Dm` can be different. @@ -11,15 +11,28 @@ The (mandatory) `Triangulation` interface can be tested with """ abstract type Triangulation{Dc,Dp} <: Grid{Dc,Dp} end +""" + get_background_model(t::Triangulation) + +Return the [`DiscreteModel`](@ref) the triangulation is associated with. +""" function get_background_model(t::Triangulation) @abstractmethod end +""" + get_grid(t::Triangulation) + +Return the [`Grid`](@ref) the triangulation is built on. +""" function get_grid(t::Triangulation) @abstractmethod end # See possible types of glue below +""" + get_glue(t::Triangulation,::Val{d}) +""" function get_glue(t::Triangulation,::Val{d}) where d nothing end @@ -53,6 +66,9 @@ get_cell_reffe(trian::Triangulation) = get_cell_reffe(get_grid(trian)) is_first_order(trian::Triangulation) = is_first_order(get_grid(trian)) # This is the most used glue, but others are possible, see e.g. SkeletonGlue. +""" + struct FaceToFaceGlue{A,B,C} +""" struct FaceToFaceGlue{A,B,C} tface_to_mface::A tface_to_mface_map::B @@ -86,7 +102,7 @@ end """ best_target(trian1::Triangulation,trian2::Triangulation) - If possible, returns a `Triangulation` to which `CellDatum` objects can be transferred + If possible, returns a `Triangulation` to which `CellDatum` objects can be transferred from `trian1` and `trian2`. Can be `trian1`, `trian2` or a new `Triangulation`. """ function best_target(trian1::Triangulation,trian2::Triangulation) @@ -108,6 +124,9 @@ function best_target( Triangulation(ReferenceFE{D},model) end +""" + get_active_model(t::Triangulation) +""" function get_active_model(t::Triangulation) compute_active_model(t) end @@ -126,13 +145,18 @@ function compute_active_model(t::Triangulation) _restrict(model,get_grid(t),glue.tface_to_mface) end -# This is the most basic Triangulation -# It represents a physical domain built using the faces of a DiscreteModel struct BodyFittedTriangulation{Dt,Dp,A,B,C} <: Triangulation{Dt,Dp} model::A grid::B tface_to_mface::C injective::Bool + + @doc """ + BodyFittedTriangulation(model::DiscreteModel, grid::Grid, tface_to_mface) + + This is the most basic Triangulation, it represents a physical domain built + using the faces of a DiscreteModel + """ function BodyFittedTriangulation(model::DiscreteModel,grid::Grid,tface_to_mface) Dp = num_point_dims(model) @assert Dp == num_point_dims(grid) @@ -159,7 +183,7 @@ function get_glue(trian::BodyFittedTriangulation{Dt},::Val{Dt}) where Dt # Case 2: The triangulation spans part of the model injectively mface_to_tface = PosNegPartition(trian.tface_to_mface,Int32(n_mfaces)) else - # Case 3: The triangulation is non-injective, so we cannot map information + # Case 3: The triangulation is non-injective, so we cannot map information # back to the model (1-way glue model -> triangulation) mface_to_tface = nothing end @@ -237,6 +261,11 @@ function Triangulation(trian::Triangulation,tface_filter::AbstractArray{<:Bool}) view(trian,sface_to_tface) end +""" + Interior(args...; kwargs...) + +Alias for [`Triangulation`](@ref)(args...; kwargs...). +""" function Interior(args...;kwargs...) Triangulation(args...;kwargs...) end @@ -247,6 +276,8 @@ function restrict(a::AbstractArray,b::AbstractArray) lazy_map(Reindex(a),b) end +""" +""" function extend(tface_to_val,mface_to_tface) @notimplemented end @@ -262,8 +293,8 @@ function extend(tface_to_val,mface_to_tface::PosNegPartition) end # NOTE: The following is needed to properly extend FEFunctions, in cases where the FESpace -# is defined on weird Triangulations (see e.g. issue #1085). -# The main purpose is to ensure we obtain operations of VoidBasis, not VoidField. I.e +# is defined on weird Triangulations (see e.g. issue #1085). +# The main purpose is to ensure we obtain operations of VoidBasis, not VoidField. I.e # we want to dispatch down to `_pos_neg_data_basis` (see below). function extend(a::LazyArray{<:Fill{typeof(transpose)}},b::PosNegPartition) @@ -278,13 +309,13 @@ function extend(a::LazyArray{<:Fill{typeof(linear_combination)}},b::PosNegPartit lazy_map(linear_combination,d1,d2) end -function extend(a::LazyArray{<:Fill{<:Broadcasting{<:Operation}}},b::PosNegPartition) +function extend(a::LazyArray{<:Fill{<:Broadcasting{<:Operation}}},b::PosNegPartition) k = a.maps.value args = map(i->extend(i,b),a.args) lazy_map(k,args...) end -function extend(a::LazyArray{<:Fill{<:Broadcasting{typeof(∘)}}},b::PosNegPartition) +function extend(a::LazyArray{<:Fill{<:Broadcasting{typeof(∘)}}},b::PosNegPartition) k = a.maps.value args = map(i->extend(i,b),a.args) lazy_map(k,args...) @@ -429,6 +460,10 @@ function _compose_glues(rglue::FaceToFaceGlue,dglue::FaceToFaceGlue) FaceToFaceGlue(dface_to_mface,dface_to_mface_map,mface_to_dface) end +""" + struct GenericTriangulation{Dc,Dp,A,B,C} <: Triangulation{Dc,Dp} + GenericTriangulation(grid, model=nothing, glue=(nothing,...)) +""" struct GenericTriangulation{Dc,Dp,A,B,C} <: Triangulation{Dc,Dp} grid::A model::B diff --git a/src/Gridap.jl b/src/Gridap.jl index ecac8cb13..49cb91e2b 100644 --- a/src/Gridap.jl +++ b/src/Gridap.jl @@ -22,8 +22,20 @@ The module is structured in the following sub-modules: - [`Gridap.ODEs`](@ref) - [`Gridap.Adaptivity`](@ref) -The exported names are: -$(EXPORTS) +$(Helpers.public_names_in_md(@__MODULE__; change_link=Dict( + :∇ => "gradient", + :∫ => "CellData.Integrand", + :⊗ => "Gridap.TensorValues.outer", + :⊙ => "Gridap.TensorValues.inner", + :× => "cross", + :⋅ => "dot", + :⋅¹ => "dot", + :⋅² => "Gridap.TensorValues.double_contraction", + :ReferenceDomain => "DomainStyle", + :PhysicalDomain => "DomainStyle", + :nedelec1 => "nedelec", + :modal_serendipity => "modal_lagrangian", +))) """ module Gridap diff --git a/src/Helpers/HelperFunctions.jl b/src/Helpers/HelperFunctions.jl index 5fb5cda3d..7af4a3e68 100644 --- a/src/Helpers/HelperFunctions.jl +++ b/src/Helpers/HelperFunctions.jl @@ -38,3 +38,51 @@ function first_and_tail(a::Tuple) first(a), Base.tail(a) end +""" + public_names_in_md(m::Module[; change_link=Dict()) + +Return a string displaying exported and other public names of the module for +printing in markdown, as a reference ```"[`name`](@ref)"```. +If the dictionary `change_link` has a key for `name`, then it is used as a link +instead of `name` itself: ```"[`name`](@ref \$(change_link[name]) )"``` +""" +function public_names_in_md(m::Module; change_link=Dict()) + publics = filter(!=(nameof(m)), names(m)) + exported = filter(n->Base.isexported(m,n), publics) + non_exported_publics = filter(∉(exported), publics) + + isempty(exported) && return "" + + s = """ + ### Exported names + + """ + + for name in exported + if haskey(change_link, name) + s *= " [`" * String(name) * "`](@ref " * change_link[name] * ")," + else + s *= " [`"*String(name)*"`](@ref)," + end + end + + isempty(non_exported_publics) && return s + + s *= """ + + + ### Other public names + + """ + + for name in non_exported_publics + if haskey(change_link, name) + s *= " [`" * String(name) * "`](@ref " * change_link[name] * ")," + else + s *= " [`" * String(name) * "`](@ref)," + end + end + s *= "\n" + + return s +end diff --git a/src/Helpers/Helpers.jl b/src/Helpers/Helpers.jl index e2e7f3d3d..15e26a1de 100644 --- a/src/Helpers/Helpers.jl +++ b/src/Helpers/Helpers.jl @@ -1,10 +1,7 @@ """ This module provides a set of helper macros and helper functions -The exported macros are: - -$(EXPORTS) - +$(public_names_in_md(@__MODULE__)) """ module Helpers using DocStringExtensions @@ -26,6 +23,7 @@ export get_val_parameter export first_and_tail export GridapType export set_debug_mode, set_performance_mode +export public_names_in_md #export operate include("Preferences.jl") diff --git a/src/Helpers/Macros.jl b/src/Helpers/Macros.jl index b27821144..d29529ab9 100644 --- a/src/Helpers/Macros.jl +++ b/src/Helpers/Macros.jl @@ -4,7 +4,7 @@ Macro used in generic functions that must be overloaded by derived types. """ -macro abstractmethod(message="This function belongs to an interface definition and cannot be used.") +macro abstractmethod(message="This method belongs to an abstract interface definition and cannot be used.") quote error($(esc(message))) end @@ -16,7 +16,7 @@ end Macro used to raise an error, when something is not implemented. """ -macro notimplemented(message="This function is not yet implemented") +macro notimplemented(message="This method is not yet implemented.") quote error($(esc(message))) end @@ -28,7 +28,7 @@ end Macro used to raise an error if the `condition` is true """ -macro notimplementedif(condition,message="This function is not yet implemented") +macro notimplementedif(condition,message="This method is not yet implemented.") quote if $(esc(condition)) @notimplemented $(esc(message)) diff --git a/src/Io/Io.jl b/src/Io/Io.jl index f8ed74aa1..5f6febd2d 100644 --- a/src/Io/Io.jl +++ b/src/Io/Io.jl @@ -1,7 +1,6 @@ """ -The exported names in this module are: -$(EXPORTS) +$(public_names_in_md(@__MODULE__)) """ module Io diff --git a/src/MultiField/MultiField.jl b/src/MultiField/MultiField.jl index b660ab70d..f8c4e5db1 100644 --- a/src/MultiField/MultiField.jl +++ b/src/MultiField/MultiField.jl @@ -1,7 +1,6 @@ """ -The exported names are -$(EXPORTS) +$(public_names_in_md(@__MODULE__)) """ module MultiField diff --git a/src/MultiField/MultiFieldCellFields.jl b/src/MultiField/MultiFieldCellFields.jl index ed7b8a613..674be6596 100644 --- a/src/MultiField/MultiFieldCellFields.jl +++ b/src/MultiField/MultiFieldCellFields.jl @@ -1,7 +1,13 @@ +""" + struct MultiFieldCellField{DS<:DomainStyle} <: CellField +""" struct MultiFieldCellField{DS<:DomainStyle} <: CellField single_fields::Vector{<:CellField} domain_style::DS + """ + MultiFieldCellField(single_fields::Vector{<:CellField}) + """ function MultiFieldCellField(single_fields::Vector{<:CellField}) @assert length(single_fields) > 0 is_ref = any(sf -> isa(DomainStyle(sf), ReferenceDomain), single_fields) @@ -15,8 +21,9 @@ function CellData.get_data(f::MultiFieldCellField) Function get_data is not implemented for MultiFieldCellField at this moment. You need to extract the individual fields and then evaluate them separately. - If ever implemented, evaluating a `MultiFieldCellField` directly would provide, - at each evaluation point, a tuple with the value of the different fields. + If ever implemented, evaluating a [`MultiFieldCellField`](@ref) directly would + provide, at each evaluation point, a tuple with the value of the different + fields. """ @notimplemented s end diff --git a/src/MultiField/MultiFieldFESpaces.jl b/src/MultiField/MultiFieldFESpaces.jl index 0f3c7da5d..5de42fa8e 100644 --- a/src/MultiField/MultiFieldFESpaces.jl +++ b/src/MultiField/MultiFieldFESpaces.jl @@ -1,3 +1,5 @@ +""" +""" abstract type MultiFieldStyle end """ @@ -12,13 +14,13 @@ struct ConsecutiveMultiFieldStyle <: MultiFieldStyle end struct BlockMultiFieldStyle{NB,SB,P} <: MultiFieldStyle end Similar to ConsecutiveMultiFieldStyle, but we keep the original DoF ids of the -individual spaces for better block assembly (see BlockSparseMatrixAssembler). +individual spaces for better block assembly (see [`BlockSparseMatrixAssembler`](@ref)). Takes three parameters: - - NB: Number of assembly blocks - - SB: Size of each assembly block, as a Tuple. - - P : Permutation of the variables of the multifield space when assembling, as a Tuple. + - NB: Number of assembly blocks + - SB: Size of each assembly block, as a Tuple. + - P : Permutation of the variables of the multifield space when assembling, as a Tuple. """ struct BlockMultiFieldStyle{NB,SB,P} <: MultiFieldStyle end @@ -125,7 +127,10 @@ struct MultiFieldFESpace{MS<:MultiFieldStyle,CS<:ConstraintStyle,V} <: FESpace end """ - MultiFieldFESpace(spaces::Vector{<:SingleFieldFESpace}) + MultiFieldFESpace(::Type{V}, spaces::Vector{<:SingleFieldFESpace}) + MultiFieldFESpace( spaces::Vector{<:SingleFieldFESpace}; + style = ConsecutiveMultiFieldStyle() + ) """ function MultiFieldFESpace( spaces::Vector{<:SingleFieldFESpace}; style = ConsecutiveMultiFieldStyle() diff --git a/src/ODEs/ODEOperators.jl b/src/ODEs/ODEOperators.jl index 0727277ad..d7f40dc24 100644 --- a/src/ODEs/ODEOperators.jl +++ b/src/ODEs/ODEOperators.jl @@ -7,6 +7,9 @@ Trait that indicates the linearity type of an ODE operator. """ abstract type ODEOperatorType <: GridapType end +""" + struct NonlinearODE <: ODEOperatorType end +""" struct NonlinearODE <: ODEOperatorType end """ @@ -22,6 +25,9 @@ where `N` is the order of the ODE operator, `∂t^k[u]` is the `k`-th-order time derivative of `u`, and both `mass` and `res` have order `N-1`. """ abstract type AbstractQuasilinearODE <: ODEOperatorType end +""" + struct QuasilinearODE <: AbstractQuasilinearODE end +""" struct QuasilinearODE <: AbstractQuasilinearODE end """ @@ -37,6 +43,9 @@ where `N` is the order of the ODE operator, `∂t^k[u]` is the `k`-th-order time derivative of `u`, `mass` is independent of `u` and `res` has order `N-1`. """ abstract type AbstractSemilinearODE <: AbstractQuasilinearODE end +""" + struct SemilinearODE <: AbstractSemilinearODE end +""" struct SemilinearODE <: AbstractSemilinearODE end """ @@ -50,6 +59,9 @@ where `N` is the order of the ODE operator, and `∂t^k[u]` is the `k`-th-order time derivative of `u`. """ abstract type AbstractLinearODE <: AbstractSemilinearODE end +""" + struct LinearODE <: AbstractLinearODE end +""" struct LinearODE <: AbstractLinearODE end ################ diff --git a/src/ODEs/ODESolvers.jl b/src/ODEs/ODESolvers.jl index b00fa297b..503ef85c2 100644 --- a/src/ODEs/ODESolvers.jl +++ b/src/ODEs/ODESolvers.jl @@ -178,6 +178,13 @@ function _setindex_all!(a::CompressedArray, v, i::Integer) a end +""" + RungeKutta(sysslvr_nl::NonlinearSolver, sysslvr_l::NonlinearSolver, dt::Real, tableau::AbstractTableau) + RungeKutta(sysslvr_nl::NonlinearSolver, dt::Real, tableau) + RungeKutta(sysslvr_nl::NonlinearSolver, sysslvr_l::NonlinearSolver, dt::Real, name::Symbol) + +The second constructor uses `sysslvr_nl` for the `sysslvr_l` argument. +""" function RungeKutta( sysslvr_nl::NonlinearSolver, sysslvr_l::NonlinearSolver, dt::Real, tableau::AbstractTableau diff --git a/src/ODEs/ODESolvers/GeneralizedAlpha2.jl b/src/ODEs/ODESolvers/GeneralizedAlpha2.jl index 803e63dae..456d068c5 100644 --- a/src/ODEs/ODESolvers/GeneralizedAlpha2.jl +++ b/src/ODEs/ODESolvers/GeneralizedAlpha2.jl @@ -43,6 +43,9 @@ function GeneralizedAlpha2(sysslvr::NonlinearSolver, dt::Real, ρ∞::Real) GeneralizedAlpha2(sysslvr, dt, αf, αm, γ, β) end +""" + Newmark(sysslvr::NonlinearSolver, dt::Real, γ::Real, β::Real) +""" function Newmark(sysslvr::NonlinearSolver, dt::Real, γ::Real, β::Real) γ01 = clamp(γ, 0, 1) if γ01 != γ diff --git a/src/ODEs/ODESolvers/Tableaus.jl b/src/ODEs/ODESolvers/Tableaus.jl index 59d068e6c..9c9920641 100644 --- a/src/ODEs/ODESolvers/Tableaus.jl +++ b/src/ODEs/ODESolvers/Tableaus.jl @@ -237,10 +237,18 @@ function Polynomials.get_order(tableau::IMEXTableau) tableau.imex_order end +""" + get_imex_tableaus(tableau::IMEXTableau) + +Return the pair of implicit and explicit tableaus of the given IMEX tableau. +""" function get_imex_tableaus(tableau::IMEXTableau) (tableau.im_tableau, tableau.ex_tableau) end +""" + is_padded(tableau::IMEXTableau) -> Bool +""" function is_padded(tableau::IMEXTableau) tableau.is_padded end @@ -284,6 +292,9 @@ include("TableausDIM.jl") include("TableausIMEX.jl") +""" +List of available Butcher tableaus. +""" const available_tableaus = [ :EXRK_Euler_1_1, :EXRK_Midpoint_2_2, @@ -324,6 +335,9 @@ const available_tableaus = [ :SDIRK_4_3, ] +""" +List of available tableaus for IMEX schemes. +""" const available_imex_tableaus = [ :IMEXRK_1_1_1, :IMEXRK_1_2_1, diff --git a/src/ODEs/ODESolvers/ThetaMethod.jl b/src/ODEs/ODESolvers/ThetaMethod.jl index e350bf18d..2dca304de 100644 --- a/src/ODEs/ODESolvers/ThetaMethod.jl +++ b/src/ODEs/ODESolvers/ThetaMethod.jl @@ -35,7 +35,13 @@ struct ThetaMethod <: ODESolver end end +""" + MidPoint(sysslvr, dt) = ThetaMethod(sysslvr, dt, 0.5) +""" MidPoint(sysslvr, dt) = ThetaMethod(sysslvr, dt, 0.5) +""" + BackwardEuler(sysslvr, dt) = ThetaMethod(sysslvr, dt, 1) +""" BackwardEuler(sysslvr, dt) = ThetaMethod(sysslvr, dt, 1) ################## diff --git a/src/ODEs/ODEs.jl b/src/ODEs/ODEs.jl index 30f294d5d..2be3e347d 100644 --- a/src/ODEs/ODEs.jl +++ b/src/ODEs/ODEs.jl @@ -1,7 +1,6 @@ """ -The exported names are -$(EXPORTS) +$(public_names_in_md(@__MODULE__)) """ module ODEs diff --git a/src/ODEs/TransientFEOperators.jl b/src/ODEs/TransientFEOperators.jl index 0d3455a87..a98319cf1 100644 --- a/src/ODEs/TransientFEOperators.jl +++ b/src/ODEs/TransientFEOperators.jl @@ -343,6 +343,9 @@ struct TransientQuasilinearFEOpFromWeakForm <: TransientFEOperator{QuasilinearOD end # Constructor with manual jacobians +""" + +""" function TransientQuasilinearFEOperator( mass::Function, res::Function, jacs::Tuple{Vararg{Function}}, trial, test; @@ -364,6 +367,9 @@ function TransientQuasilinearFEOperator( end # Constructor with flat arguments (orders 0, 1, 2) +""" + +""" function TransientQuasilinearFEOperator( mass::Function, res::Function, jac::Function, @@ -377,6 +383,16 @@ function TransientQuasilinearFEOperator( ) end +""" + TransientQuasilinearFEOperator(mass, res, jac, trial, test[; assembler]) + TransientQuasilinearFEOperator(mass, res, jac, jac_t, trial, test[; assembler]) + TransientQuasilinearFEOperator(mass, res, jac, jac_t, jac_tt, trial, test[; assembler]) + TransientQuasilinearFEOperator(mass, res, jacs::Tuple, trial, test[; assembler]) + TransientQuasilinearFEOperator(mass, res, trial, test; order=1,[ assembler]) + +Where `assembler` defaults to `SparseMatrixAssembler`(`trial`, `test)`. +Returns a [`TransientQuasilinearFEOpFromWeakForm`](@ref). +""" function TransientQuasilinearFEOperator( mass::Function, res::Function, jac::Function, jac_t::Function, @@ -521,6 +537,17 @@ function TransientSemilinearFEOperator( end # Constructor with flat arguments (orders 0, 1, 2) +""" + TransientSemilinearFEOperator(mass, res, jac, trial, test; [constant_mass][, assembler]) + TransientSemilinearFEOperator(mass, res, jac, jac_t, trial, test; [constant_mass][, assembler]) + TransientSemilinearFEOperator(mass, res, jac, jac_t, jac_tt, trial, test; [constant_mass][, assembler]) + TransientSemilinearFEOperator(mass, res, jacs::Tuple, trial, test; [constant_mass][, assembler]) + TransientSemilinearFEOperator(mass, res, trial, test; order=1[, constant_mass][, assembler]) + +Where `constant_mass` defaults to `false` and `assembler` defaults to +`SparseMatrixAssembler`(`trial`, `test)`. +Returns a [`TransientSemilinearFEOpFromWeakForm`](@ref). +""" function TransientSemilinearFEOperator( mass::Function, res::Function, jac::Function, @@ -665,6 +692,8 @@ struct TransientLinearFEOpFromWeakForm <: TransientFEOperator{LinearODE} end # Constructor with manual jacobians +""" +""" function TransientLinearFEOperator( forms::Tuple{Vararg{Function}}, res::Function, jacs::Tuple{Vararg{Function}}, trial, test; @@ -699,6 +728,15 @@ function TransientLinearFEOperator( end # Constructor with flat forms and automatic jacobians (orders 0, 1, 2) +""" + TransientLinearFEOperator(mass, res, trial, test; constant_forms=(false,)[, assembler]) + TransientLinearFEOperator(stiffness, mass, res, trial, test; constant_forms=(false, false)[, assembler]) + TransientLinearFEOperator(stiffness, damping, mass, res, trial, test; constant_forms=(false, false, false)[, assembler]) + TransientLinearFEOperator(forms, res, trial, test; constant_forms=(false, ...)[, assembler]) + +where assembler defaults to `SparseMatrixAssembler`(`trial`, `test`). +Returns a [`TransientLinearFEOpFromWeakForm`](@ref). +""" function TransientLinearFEOperator( mass::Function, res::Function, trial, test; diff --git a/src/ODEs/TransientFESpaces.jl b/src/ODEs/TransientFESpaces.jl index 2b5d77869..e67645f90 100644 --- a/src/ODEs/TransientFESpaces.jl +++ b/src/ODEs/TransientFESpaces.jl @@ -161,6 +161,12 @@ end # MultiFieldFESpace # ##################### # This is only for backward compatibility, we could remove it +""" + const TransientMultiFieldFESpace = MultiFieldFESpace + +!!! warning + Deprecated (use MultiFieldFESpace) +""" const TransientMultiFieldFESpace = MultiFieldFESpace function has_transient(U::MultiFieldFESpace) diff --git "a/src/Polynomials/BarycentricP\316\233Bases.jl" "b/src/Polynomials/BarycentricP\316\233Bases.jl" new file mode 100644 index 000000000..b34c45277 --- /dev/null +++ "b/src/Polynomials/BarycentricP\316\233Bases.jl" @@ -0,0 +1,1146 @@ +# The theory and notations used in this file are documented in detail in the +# Bernstein bases algorithms Developper notes of the official documentation. + +""" + FEEC_space_definition_checks(::Val{D}, T, r, k, F, rotate_90; cart_prod=false) + +Check if the argument define a valid Finite Element Exterior Calculus (FEEC) polynomial space, +as defined in the Periodic Table of the Finite Elements, `FᵣΛᵏ` in dimension `D`. + +The arguments are also described in [`FEEC_poly_basis`](@ref). +""" +function FEEC_space_definition_checks( + ::Val{D},::Type{T},r::Integer,k::Integer,F::Symbol, rotate_90::Bool=false, DG_calc::Bool=false; + cart_prod=false +) where {D,T} + + if cart_prod + @check k in (0, D) "Cartesian product of polynomial basis only possible for `k` = 0 or `D`, got k=$k and D=$D" + @check T<:Number + else + @check T<:Real "T needs to be <:Real since represents the scalar type, got $T" + end + @check F in (:P⁻,:P,:Q⁻,:S) "F must be either :P⁻,:P,:Q⁻ or :S, got $F." + @check k in 0:D "The form order k must be in 0:D, got k=$k and D=$D." + @check r ≥ 0 "The polynomial order r must be positive, got $r." + + if DG_calc + @notimplemented "A new MultiValue type and associated algebraic operations ∧/⋆/𝑑/δ need to be implemented to use form valued polynomials." + elseif D>3 && ( 1 < k < D-1) + @unreachable "Vector calculus proxy of differential form bases are only available for `k`=0,1,`D`-1 or `D`, got k=$k and D=$D." + end + + if rotate_90 && !(!DG_calc && isone(k) && D==2) + @warn """The `rotate_90` kwarg only makes sense for 2D vector proxied forms + of degree 1, it will be ignored and may lead to errors. + Got k=$k, D=$D for F=$F + """ + end + true +end + +############################# +# BarycentricPΛIndices type # +############################# + +# Do print some BarycentricPΛIndices generated by PΛ_bubbles(r,k,D) in REPL before +# reading this... + +# All types and fields "XX" below should be understood as "Indices_For_XX" + +# One bubble function: +# ω_w = ( w, α, α_id, J, sub_J_ids, sup_α_ids ) +const BubbleFunction = Tuple{Int, Vector{Int}, Int, Vector{Int}, Vector{Int}, Vector{Int}} + +# One bubble space associated to the d-dimensional face F +# bubble_d_F = ( F, F_bubble_functions ) +const Bubble = Tuple{Vector{Int}, Vector{BubbleFunction}} + +""" + struct BarycentricPΛIndices + identity::UInt + bubbles::Vector{Bubble} + components::Vector{Tuple{ Int, Vector{Int}, Int}} + end + +For storing indices in `BarycentricP(m)ΛBasis`. The `bubbles` are generated by +[`PΛ_bubbles`](@ref) or [`PmΛ_bubbles`](@ref), the `components` by +[`_basis_forms_components`](@ref). +""" +struct BarycentricPΛIndices + # An objectid is stored here to keep track of what's inside and perform a + # sanity check when reusing the indices elsewhere: + # identity = objectid( (r,k,D,:P,false,false) ) + # for a PᵣΛᵏ(△ᴰ) basis indices with vector-proxied components and rotate_90=false. + identity::UInt + + # bubble indices given by P(m)Λ_bubbles (can be filtered and renumbered to select subset of bubbles) + # The BubbleFunction[1] "w" MUST be in increasing order when iterating over bubbles and bubble_functions + bubbles::Vector{Bubble} + + # Indices for the components of a basis k-form or its vector calculus proxy, + # (I_id, I, sgnIcomp), see _basis_forms_components . + components::Vector{Tuple{ Int, Vector{Int}, Int}} + + # Components "I" of the exterior derivative of a basis polynomial form + # ext_deriv_components::Vector{Component} +end + +function _check_PΛ_indices(r,k,D,F,DG_style, indices::BarycentricPΛIndices, rot_90) + @check F ∈ (:P,:P⁻) + # Changing one of those requires re-computing all fields of BarycentricPΛIndices, + # otherwise wrong result or segfault due to @inbounds in evaluating functions + # are expected + @assert objectid( (r,k,D,F,DG_style,rot_90) ) == indices.identity + + @check begin + ordered_bf_ids = [ bubfun[1] for bub in indices.bubbles for bubfun in bub[2] ] + C = length(ordered_bf_ids) + ordered_bf_ids == 1:C + end "Invalid BarycentricPΛIndices: bubble functions are not numbered from 1 to length(b) in the bubble indices" + + @check begin + unique_faces = Set() + for bubble in indices.bubbles + push!(unique_faces, sort(bubble[1])) + end + length(unique_faces) == length(indices.bubbles) + end """ + Invalid BarycentricPΛIndices: all bubble functions to a face must be + gathered in a unique Bubble, duplicated bubble faces were found in the given + indices + """ +end + +""" + _last_bubble_function_index(ids::BarycentricPΛIndices) + +The index of the last bubble function in `ids`, that is also the number of basis +polynomial defined by `ids`, and cardinal of the owning basis. +""" +function _last_bubble_function_index(ids::BarycentricPΛIndices) + isempty(ids.bubbles) && return 0 + return ids.bubbles[end][2][end][1] +end + +function Base.show(io::IO, ::MIME"text/plain", indices::BarycentricPΛIndices) + if isempty(indices.bubbles) + print(io,"Empty PΛ basis indices") + return + end + + α = indices.bubbles[1][2][1][2] + J = indices.bubbles[1][2][1][4] + I = indices.components[1][2] + + r = sum(α)+length(J)-length(I) + k = length(I) + D = length(α)-1 + is_Pm = r != sum(α) + println(io,"PᵣΛᵏ(△ᴰ) basis indices, r=$r k=$k D=$D") + + println(io) + println(io,"Basis polynomial components") + println(io,"\tI_id\t I\t I_sgn") + for (I_id, I, I_sgn) in indices.components + println(io,"\t$I_id\t $(join(I))\t $I_sgn") + end + + println(io) + println(io,"\tw \tα \tα_id \tJ", is_Pm ? "\tsub_J_ids \tsup_α_ids" : "") + for (F, F_bubble) in indices.bubbles + isempty(F_bubble) && continue + + println(io,"Bubble of face F=$(join(F))") + for (w, α, α_id, J, sub_J_ids, sup_α_ids) in F_bubble + println(io,"\t$w \t$(join(α)) \t$α_id \t$(join(J)) \t$(join(sub_J_ids,",")) \t\t$(join(sup_α_ids,","))") + end + end + println(io) + println(io,"PᵣΛᵏ(△ᴰ) basis indices, r=$r k=$k D=$D") +end + + +########################################### +# BarycentricPmΛBasis nD polynomial bases # +########################################### + +""" + BarycentricPmΛBasis{D,V,LN,B} <: PolynomialBasis{D,V,Bernstein} + +Finite Element Exterior Calculus polynomial basis for the spaces P⁻`ᵣ`Λ`ᴷ` on +`D`-dimensional simplices, but with polynomial forms explicitely transformed +into vectors using the standard equivalence with usual vector calculus defined +in terms of the hodge star operator ⋆ and the sharp map ♯, see +[`_basis_forms_components`](@ref) (the simplex is assumed Euclidean). + +- `V` is `VectorValue{L,T}` where `L` is binomial(`D`,`k`), +- `B` is the concrete type of the `BernsteinBasisOnSimplex` necessary for the evaluation of the polynomials. + +The number of basis polynomials is binomial(`r`+`k`-1,`k`)*binomial(`D`+`r`,`D`-`k`) if no filtered bubble indices are given. + +Reference: D.N. Arnold, R.S. Falk & R. Winther, Geometric decompositions and local bases for spaces of finite element differential forms, CMAME, 2009 +""" +struct BarycentricPmΛBasis{D,V,LN,B} <: PolynomialBasis{D,V,Bernstein} + r::Int + k::Int + scalar_bernstein_basis::B + m::SVector{LN,V} + _indices::BarycentricPΛIndices + + function BarycentricPmΛBasis{D}(::Type{T}, r, k, vertices=nothing; + DG_calc=false, indices=nothing, rotate_90=false) where {D,T} + + FEEC_space_definition_checks(Val(D), T, r, k, :P⁻, rotate_90, DG_calc) + _simplex_vertices_checks(Val(D), vertices) + + indices = _generate_or_check_PmΛ_indices(r,k,D,DG_calc,indices,rotate_90) + + L = binomial(D,k) # Number of components of a basis form + V = VectorValue{L,T} # To update once DG_calc is implemented + + b = BernsteinBasisOnSimplex{D}(T, r, vertices) + B = typeof(b) + LN = binomial(D+1,k) # Number of k-faces J of a D-dimensional tetrahedron + m = zero(MVector{LN,V}) + _compute_PmΛ_basis_coefficients!(m,Val(k),D,b,vertices,indices) + + if isone(L) && !DG_calc + V = T + m = reinterpret(T, m) + end + + new{D,V,LN,B}(r,k,b,m,indices) + end + + @doc """ + BarycentricPmΛBasis(b::BarycentricPmΛBasis, faces::Vector{Int}...) + + Create a new basis which is `b` restricted to the bubble spaces for F ∈ `faces`. + """ + function BarycentricPmΛBasis(_b::BarycentricPmΛBasis{D,V,LN,B}, faces::Vector{Int}...) where {D,V,LN,B} + # Notation: _old, new + _indices = _b._indices + _bubbles = _indices.bubbles + bubbles = similar(_bubbles, length(faces)) + + w = 1 + for (Fid, F) in enumerate(faces) + F_id = findfirst(bub -> bub[1] == F, _bubbles) + @assert !isnothing(F_id) "No bubble associated to face $F in the given basis" + + _F_bubfuns = _bubbles[F_id][2] + F_bubfuns = similar(_F_bubfuns) + for (i, _fun) in enumerate(_F_bubfuns) + # re-use all α,J, etc... to minimise allocation, just change w + F_bubfuns[i] = (w, _fun[2:end]...) + w += 1 + end + bubbles[Fid] = (F, F_bubfuns) + end + + indices = BarycentricPΛIndices(_indices.identity, bubbles, _indices.components) + new{D,V,LN,B}(_b.r, _b.k, _b.scalar_bernstein_basis, _b.m, indices) + end + + function BarycentricPmΛBasis{D,V,LN,B}() where {D,V,LN,B} # just for testvalue + indices = _generate_or_check_PmΛ_indices(1,0,0,false,nothing,false) + new{D,V,LN,B}(0,0,testvalue(B),zero(SVector{LN,V}),indices) + end +end + +""" + BarycentricPmΛBasis(::Val{D}, T, r, k, vertices=nothing; kwargs...) + +Constructors for [`BarycentricPmΛBasis`](@ref) of scalar type `T`. +If `vertices` are specified, they must define a non-degenerate simplex, c.f. [`BernsteinBasisOnSimplex`](@ref). + +The kwargs are the following: +- `indices::BarycentricPΛIndices = nothing`: may be provided to avoid allocations of new indices, or to select specific bubbles spaces, +- `DG_calc = false`: set to `true` to choose `k`-form valued polynomials instead of vector valued polynomials (not implemented yet), +- `rotate_90 = false`: In 2`D` for `k`=1, `true` to apply a 90° rotation of the vector proxied polynomials ((x,y) -> (-y,x)), needed for Raviart-Thomas/BDM. +""" +function BarycentricPmΛBasis(::Val{D},::Type{T},r,k,vertices=nothing; kwargs...) where {D,T} + BarycentricPmΛBasis{D}(T,r,k,vertices; kwargs...) +end + +#get_FEEC_poly_degree(b::BarycentricPmΛBasis) = b.r +#get_FEEC_form_degree(b::BarycentricPmΛBasis) = b.k +#get_FEEC_family(::BarycentricPmΛBasis) = :P⁻ + +Base.size(b::BarycentricPmΛBasis) = (_last_bubble_function_index(b._indices), ) + +function testvalue(::Type{BarycentricPmΛBasis{D,V,LN,B}}) where {D,V,LN,B} + BarycentricPmΛBasis{D,V,LN,B}() +end + +########################################## +# BarycentricPΛBasis nD polynomial bases # +########################################## + +""" + BarycentricPΛBasis{D,V,C,B} <: PolynomialBasis{D,V,Bernstein} + +Finite Element Exterior Calculus polynomial basis for the spaces P`ᵣ`Λ`ᴷ` on +`D`-dimensional simplices, but with polynomial forms explicitely transformed +into vectors using the standard equivalence with usual vector calculus defined +in terms of the hodge star operator ⋆ and the sharp map ♯, see +[`_basis_forms_components`](@ref) (the simplex is assumed Euclidean). + +- `V` is `VectorValue{L,T}` where `L=binomial(D,k)`, +- `C` is the number of basis polynomials, +- `B` is the concrete type of the `BernsteinBasisOnSimplex` necessary for the evaluation of the polynomials. + +`C` = binomial(`r`+`k`,`k`)*binomial(`D`+`r`,`D`-`k`) if no custom bubble indices are given. + +Reference: D.N. Arnold, R.S. Falk & R. Winther, Geometric decompositions and local bases for spaces of finite element differential forms, CMAME, 2009 +""" +struct BarycentricPΛBasis{D,V,C,B} <: PolynomialBasis{D,V,Bernstein} + r::Int + k::Int + scalar_bernstein_basis::B + Ψ::SVector{C,V} + _indices::BarycentricPΛIndices + + function BarycentricPΛBasis{D}(::Type{T}, r, k, vertices=nothing; + DG_calc=false, indices=nothing, rotate_90=false) where {D,T} + + FEEC_space_definition_checks(Val(D), T, r, k, :P⁻, rotate_90, DG_calc) + _simplex_vertices_checks(Val(D), vertices) + + indices = _generate_or_check_PΛ_indices(r,k,D,DG_calc,indices,rotate_90) + + L = binomial(D,k) # Number of components of a basis form + V = VectorValue{L,T} # To update once DG_calc is implemented + C = _last_bubble_function_index(indices) # Cardinal of the basis + + b = BernsteinBasisOnSimplex{D}(T, r, vertices) + B = typeof(b) + Ψ = zero(MVector{C,V}) + _compute_PΛ_basis_form_coefficient!(Ψ,r,k,Val(D),b,vertices,indices) + + if isone(L) && !DG_calc + V = T + Ψ = reinterpret(T, Ψ) + end + + new{D,V,C,B}(r,k,b,Ψ,indices) + end + + @doc """ + BarycentricPΛBasis(b::BarycentricPΛBasis, faces::Vector{Int}...) + + Create a new basis which is `b` restricted to the bubble spaces for F ∈ `faces`. + The faces are represented by some `Vector{Int}` of their vertices ids, like in + [`BarycentricPΛIndices`](@ref). + """ + function BarycentricPΛBasis(_b::BarycentricPΛBasis{D,V,_C,B}, faces::Vector{Int}...) where {D,V,_C,B} + # Notation: _old, new + _indices = _b._indices + _bubbles = _indices.bubbles + _Ψ = _b.Ψ + + bubbles = similar(_bubbles, length(faces)) + Ψ = zero(MVector{_C,V}) # cache of maximum possible size + w = 1 + + for (Fid, F) in enumerate(faces) + F_id = findfirst(bub -> bub[1] == F, _bubbles) + @assert !isnothing(F_id) "No bubble associated to face $F in the given basis" + + _F_bubfuns = _bubbles[F_id][2] + F_bubfuns = similar(_F_bubfuns) + for (i, _fun) in enumerate(_F_bubfuns) + # re-use all α,J, etc... to minimise allocation, just change w + (_w, _fun_ids...) = _fun + F_bubfuns[i] = (w, _fun_ids...) + Ψ[w] = _Ψ[_w] + w += 1 + end + bubbles[Fid] = (F, F_bubfuns) + end + + C = w-1 + Ψ = SVector{C,V}( Ψ[1:C] ) + indices = BarycentricPΛIndices(_indices.identity, bubbles, _indices.components) + new{D,V,C,B}(_b.r, _b.k, _b.scalar_bernstein_basis, Ψ, indices) + end + + function BarycentricPΛBasis{D,V,C,B}() where {D,V,C,B} # Just for testvalue + indices = _generate_or_check_PΛ_indices(1,0,0,false,nothing,false) + new{D,V,C,B}(0,0,testvalue(B),zero(SVector{C,V}),indices) + end +end + +""" + BarycentricPΛBasis(::Val{D}, T, r, k, vertices=nothing; kwargs...) + +Constructors for [`BarycentricPΛBasis`](@ref) of scalar type `T`. +If `vertices` are specified, they must define a non-degenerate simplex, c.f. [`BernsteinBasisOnSimplex`](@ref). + +The kwargs are the following: +- `indices::BarycentricPΛIndices = nothing`: may be provided to avoid allocations of new indices, or to select specific bubbles spaces, +- `DG_calc = false`: set to `true` to choose `k`-form valued polynomials instead of vector valued polynomials (not implemented yet), +- `rotate_90 = false`: In 2`D` for `k`=1, `true` to apply a 90° rotation of the vector proxied polynomials ((x,y) -> (-y,x)), needed for Raviart-Thomas/BDM. +""" +function BarycentricPΛBasis(::Val{D},::Type{T},r,k,vertices=nothing; kwargs...) where {D,T} + BarycentricPΛBasis{D}(T,r,k,vertices; kwargs...) +end + +#get_FEEC_poly_degree(b::BarycentricPΛBasis) = b.r +#get_FEEC_form_degree(b::BarycentricPΛBasis) = b.k +#get_FEEC_family(::BarycentricPΛBasis) = :P + +Base.size(::BarycentricPΛBasis{D,V,C}) where {D,V,C} = (C, ) + +function testvalue(::Type{BarycentricPΛBasis{D,V,C,B}}) where {D,V,C,B} + BarycentricPΛBasis{D,V,C,B}() +end + +########################## +# Common Implementation # +########################## + +const _BaryPΛBasis{D} = Union{BarycentricPmΛBasis{D}, BarycentricPΛBasis{D}} + +""" + get_bubbles(b::BarycentricPmΛBasis) + get_bubbles(b::BarycentricPΛBasis) + +Get the iterator over the bubble spaces in the geometric decomposition of `b`, +typically generated by [`PmΛ_bubbles`](@ref) or [`PΛ_bubbles`](@ref). + +They can be vizualized using [`print_indices(b)`](@ref print_indices). +""" +get_bubbles(b::_BaryPΛBasis) = b._indices.bubbles +get_order(b::_BaryPΛBasis) = b.r +get_orders(b::_BaryPΛBasis{D}) where D = tfill(get_order(b), Val(D)) + +""" + print_indices(b::BarycentricPmΛBasis, out=stdout) + print_indices(b::BarycentricPΛBasis, out=stdout) + +Prints the indices of `b` in a user friendly format into `out`. +""" +print_indices(b::_BaryPΛBasis, out=stdout) = show(out, MIME"text/plain"(), b._indices) + +_get_cart_to_bary_matrix(b::_BaryPΛBasis) = b.scalar_bernstein_basis.cart_to_bary_matrix + +function _return_cache(b::_BaryPΛBasis,x,::Type{G},::Val{N_deriv}) where {G,N_deriv} + T = eltype(G) + np = length(x) + ndof = length(b) + ndof_bernstein = length(b.scalar_bernstein_basis) + + r = CachedArray(zeros(G,(np,ndof))) + # Cache for all scalar nD-Bernstein polynomials Bα + cB = CachedVector(zeros(T,ndof_bernstein)) + # Cache for derivatives of Bα (∇Bα or HBα) + if N_deriv > 0 + DB = T + xi = testitem(x) + for _ in 1:N_deriv + DB = gradient_type(DB,xi) + end + # Cache for N_deriv's derivatives all of scalar nD-Bernstein polynomials + t = (( nothing for _ in 2:N_deriv)..., CachedArray(zeros(DB,(1,ndof_bernstein)))) + s = MArray{Tuple{size(DB)...},T}(undef) + else + t = () + s = nothing + end + (r, s, cB, t...) +end + +function _setsize!(b::_BaryPΛBasis, np, ω, t...) + ndof = length(b) + ndof_bernstein = length(b.scalar_bernstein_basis) + setsize!(ω,(np,ndof)) + setsize!(t[1],(ndof_bernstein,)) # this is cB + if length(t) > 1 + setsize!(t[end],(1,ndof_bernstein)) # this is ∇B or HB + end +end + +function _get_static_parameters(b::_BaryPΛBasis) + r = get_order(b) + return Val(r) +end + +""" + _basis_forms_components(D,k,DG_style,rotate_90) + +If `DG_style==true`, return the triples (`I_id`, `I`, 1) for each D-dimensional +k-form components dxᴵ = dxᴵ¹ ∧ dxᴵ² ∧ ... ∧ dxᴵᵏ where `I` is a combination of +1:`D` and `I_id = _combination_index(I)`. The triples are ordered like in +[`_sorted_combinations`](@ref) (`I_id` increasing). + +If `DG_style`==false, the indices are changed to implement the vector proxy of +the differential forms ω defined by: +- ω♯ if `k` = 0 or 1 +- (⋆ω)♯ if `k` = `D`-1 or `D` and `k`>1 +where ⋆ is the hodge star operator and ♯ the sharp map. + +The triples become (`I_proxy_id`, `I`, `I_proxy_sgn`) with the same `I` (and in +the same order), such that the components of the vector proxy v of a `k`-form ω are + + v[I_proxy_id] = I_proxy_sgn * ω[I_id] + +If `k` ∈ {0,1,`D`}, the proxy is trivial. If `k`=`D`-1, ♯ reverses the +components order and ⋆ adds signs 1, -1, 1, -1 ... + +If `rotate_90` is `true` and `k` is `1`, the (⋆ω)♯ proxy is applied instead of ω♯. +""" +function _basis_forms_components(D,k,DG_style,rot_90) + components = Vector{Tuple{ Int, Vector{Int}, Int}}(undef, binomial(D,k)) + for (I_id, I) in enumerate(_sorted_combinations(D,k)) + # The rotation for 2D Raviart-Thomas/BDM is actually considering k to be D-1 + # rather than 1, that is applying ⋆. + if DG_style || iszero(k) || isone(k) && !rot_90 + components[I_id] = (I_id, I, 1) + else # if k == D, I = [1:D] and this is just (1, [], 1) (but that works) + Icomp = _complement(I, D) + Istar_id = _combination_index(Icomp) + Istar_sgn = _combination_sign(I) + components[I_id] = (Istar_id, I, Istar_sgn) + end + end + components +end + + +###################################### +# BarycentricPmΛBasis Implementation # +###################################### + +function _generate_or_check_PmΛ_indices(r,k,D,DG_style,::Nothing,rot_90) + identity = objectid( (r,k,D,:P⁻,DG_style,rot_90) ) + bubbles = PmΛ_bubbles(r,k,D) + components = _basis_forms_components(D,k,DG_style,rot_90) + return BarycentricPΛIndices(identity,bubbles,components) +end + +function _generate_or_check_PmΛ_indices(r,k,D,DG_style,indices::BarycentricPΛIndices,rot_90) + _check_PΛ_indices(r,k,D,:P⁻,DG_style,indices,rot_90) + return indices +end + +function _PmΛ_F_bubble_functions(r,k,D,F,w) + N = D + 1 + ids = BubbleFunction[] + for α in bernstein_terms(r-1,D) + sup_α_ids = _sup_multi_indices(α) + for J in _sorted_combinations(N,k+1) + sub_J_ids = _sub_combinations_ids(J) + j = _minimum_or_one(J)-1 + if issetequal(_support(α) ∪ J, F) && all(α[1:j] .== 0) + w += 1 + α_id = bernstein_term_id(α) + push!(ids, (w, α, α_id, J, sub_J_ids, sup_α_ids)) + end + end + end + ids +end + +""" + PmΛ_bubbles(r,k,D) + +Generates the indices caracterizing the basis function of `BarycentricPmΛBasis`, +described in the Bernstein bases algorithm Developper notes of the official +documentation, and are used as follows: + + for (F, bubble_functions) in PmΛ_bubbles(r,k,D) + for (w, α, α_id, J, sub_J_ids, sup_α_ids) in bubble_functions + # ... + end + end +""" +function PmΛ_bubbles(r,k,D) + w=0 + bubbles = Bubble[] + for d in k:D + for F in _sorted_combinations(D+1, d+1) + bubble_functions = _PmΛ_F_bubble_functions(r,k,D,F,w) + isempty(bubble_functions) && continue + push!(bubbles, (F, bubble_functions)) + w += length(bubble_functions) + end + end + @check w == binomial(r+k-1,k)*binomial(D+r,D-k) + bubbles +end + +function _compute_PmΛ_basis_coefficients!(m,::Val{k},D,b,vertices,indices) where k + V = eltype(m) + M = transpose(b.cart_to_bary_matrix[:,2:end]) + m_J = Mutable(V)(undef) + @inbounds for (J_id, J) in enumerate(_sorted_combinations(D+1,k)) + for (I_id, I, I_sgn) in indices.components + m_J[I_id] = I_sgn * _minor(M,I,J,Val(k)) + end + m[J_id] = m_J + end + nothing +end + +function _compute_PmΛ_basis_coefficients!( + m,::Val{k},D,b,vertices::Nothing,indices) where k + + if iszero(k) # so V is scalar, no change of basis + m .= 1 + return nothing + end + + V = eltype(m) + m_J = Mutable(V)(undef) + @inbounds for (J_id, J) in enumerate(_sorted_combinations(D+1,k)) + s = Int(isone(J[1])) + for (I_id, I, I_sgn) in indices.components + n = count(i-> (J[i]-1)∉I, (1+s):k) + if iszero(n) + p = _findfirst_val_or_zero(j-> (I[j]+1)∉J, 1, k) + m_J[I_id] = I_sgn*_minusone_if_even_else_one(p+1) + else + m_J[I_id] = 0 + end + end + m[J_id] = m_J + end + nothing +end + +# API + +function _evaluate_nd!( + b::BarycentricPmΛBasis{D}, x, + ω::AbstractMatrix, i, cB, + ::Val{r}) where {D,r} + + V = eltype(ω) + λ = _cart_to_bary(x, _get_cart_to_bary_matrix(b)) + + # _evaluate_nd!(::BernsteinBasisOnSimplex) without set_value + cB[1] = 1 + _downwards_de_Casteljau_nD!(cB,λ,Val(r-1),Val(D)) + + @inbounds for (_, bubble_functions) in get_bubbles(b) + for (w, _, α_id, J, sub_J_ids) in bubble_functions + Bα = cB[α_id] + ω_w = zero(V) + + for (l, J_sub_Jl_id) in enumerate(sub_J_ids) + sgnl = _minusone_if_even_else_one(l) + λ_j = λ[J[l]] + m_J_l = b.m[J_sub_Jl_id] + + ω_w += flipsign(λ_j,sgnl) * m_J_l + end + + ω[i,w] = Bα * ω_w + end + end +end + +function _gradient_nd!( + b::BarycentricPmΛBasis{D}, x, + ∇ω::AbstractMatrix{G}, i, cB, + ∇B::AbstractMatrix{<:VectorValue{D}}, + s::MVector{D}, + ::Val{r}) where {D,G,r} + + _gradient_nd!(b.scalar_bernstein_basis, x, ∇B, 1, cB, nothing, s, Val(r)) + + @inbounds for (_, bubble_functions) in get_bubbles(b) + for (w, α, _, J, sub_J_ids, sup_α_ids) in bubble_functions + ∇ω_w = zero(G) + + for (l, J_sub_Jl_id) in enumerate(sub_J_ids) + sgnl = _minusone_if_even_else_one(l) + Jl = J[l] + α_pJl_id = sup_α_ids[Jl] + + ∇Bα_pJl = ∇B[1,α_pJl_id] + c_α_Jl = (α[Jl]+1) * sgnl / r + m_J_l = b.m[J_sub_Jl_id] + + ∇ω_w += (c_α_Jl * ∇Bα_pJl) ⊗ m_J_l + end + + ∇ω[i,w] = ∇ω_w + end + end +end + +function _hessian_nd!( + b::BarycentricPmΛBasis{D}, x, + Hω::AbstractMatrix{G}, i, cB, + ::Nothing, + HB::AbstractMatrix{<:TensorValue{D,D}}, + s::MMatrix{D,D}, + ::Val{r}) where {D,G,r} + + _hessian_nd!(b.scalar_bernstein_basis, x, HB, 1, cB, nothing, nothing, s, Val(r)) + + @inbounds for (_, bubble_functions) in get_bubbles(b) + for (w, α, _, J, sub_J_ids, sup_α_ids) in bubble_functions + Hω_w = zero(G) + + for (l, J_sub_Jl_id) in enumerate(sub_J_ids) + sgnl = _minusone_if_even_else_one(l) + Jl = J[l] + α_pJl_id = sup_α_ids[Jl] + + HB_αJl = HB[1,α_pJl_id] + c_αJl = (α[Jl]+1) * sgnl / r + m_Jl = b.m[J_sub_Jl_id] + + Hω_w += (c_αJl * HB_αJl) ⊗ m_Jl + end + + Hω[i,w] = Hω_w + end + end +end + + +##################################### +# BarycentricPΛBasis Implementation # +##################################### + +function _generate_or_check_PΛ_indices(r,k,D,DG_style,::Nothing,rot_90) + identity = objectid( (r,k,D,:P,DG_style,rot_90) ) + bubbles = PΛ_bubbles(r,k,D) + components = _basis_forms_components(D,k,DG_style,rot_90) + return BarycentricPΛIndices(identity,bubbles,components) +end + +function _generate_or_check_PΛ_indices(r,k,D,DG_style,indices::BarycentricPΛIndices,rot_90) + _check_PΛ_indices(r,k,D,:P,DG_style,indices,rot_90) + return indices +end + +function _PΛ_F_bubble_functions(r,k,D,F,w) + N = D + 1 + bubble_functions = BubbleFunction[] + empty_vec = Int[] + for α in bernstein_terms(r,D) + for J in _sorted_combinations(N,k) + j = _minimum_or_one(setdiff(F,J))-1 + if issetequal(_support(α) ∪ J, F) && all(α[1:j] .== 0) + w += 1 + α_id = bernstein_term_id(α) + push!(bubble_functions, (w, α, α_id, J, empty_vec, empty_vec)) + end + end + end + bubble_functions +end + +""" + PΛ_bubbles(r,k,D) + +Generates the indices caracterizing the basis function of `BarycentricPΛBasis`, +described in the Bernstein bases algorithm Developper notes of the official +documentation, and are used as follows: + + for (F, bubble_functions) in PΛ_bubbles(r,k,D) + for (w, α, α_id, J) in bubble_functions + # ... + end + end +""" +function PΛ_bubbles(r,k,D) + bubbles = Bubble[] + if r == 0 + F = [1:(D+1)...] + α = zeros(Int,D+1) + bubble_functions = BubbleFunction[] + for i in 1:binomial(D,k) + push!(bubble_functions, (i, α, 1, [], [], [])) # J has no meaning there + end + push!(bubbles, (F, bubble_functions)) + return bubbles + end + # r > 0 + w=0 + for d in k:D + for F in _sorted_combinations(D+1, d+1) + bubble_functions = _PΛ_F_bubble_functions(r,k,D,F,w) + isempty(bubble_functions) && continue + push!(bubbles, (F, bubble_functions)) + w += length(bubble_functions) + end + end + @check w == binomial(r+k,k)*binomial(D+r,D-k) + bubbles +end + +function _compute_PΛ_basis_form_coefficient!(Ψ,r,k,::Val{D},b,vertices,indices) where D + N = D+1 + Vk = Val(k) + V = eltype(Ψ) + + iszero(r) && return _order_0_Ψ!(Ψ) + + T = eltype(V) + α_prec = ntuple(_->-1, N) + φ_αF = MMatrix{D,N,T}(undef) + Ψw = Mutable(V)(undef) + @inbounds for (F, bubble_functions) in indices.bubbles + for (w, α, _, J) in bubble_functions + if α ≠ α_prec + _update_φ_αF!(φ_αF,b,α,F,r) + α_prec = α + end + + for (I_id, I, I_sgn) in indices.components + Ψw[I_id] = I_sgn * _minor(φ_αF,I,J,Vk) + end + Ψ[w] = Ψw + end + end + nothing +end + +@inline function _update_φ_αF!(φ_αF,b,α,F,r) + M = b.cart_to_bary_matrix + @inbounds for ci in CartesianIndices(φ_αF) + i, j = ci[1], ci[2] + mF = sum(M[Fl,i+1] for Fl in F; init=0) + φ_αF[ci] = M[j,i+1] - α[j]*mF/r + end +end + +function _order_0_Ψ!(Ψ) + V = eltype(Ψ) + T = eltype(V) + for w in eachindex(Ψ) + Ψ[w] = ntuple( i -> T(i==w), length(V)) + end + nothing +end + +function _compute_PΛ_basis_form_coefficient!( + Ψ,r,k,::Val,b,vertices::Nothing,indices) + + Vk = Val(k) + V = eltype(Ψ) + T = eltype(V) + Ψw = Mutable(V)(undef) + + iszero(r) && return _order_0_Ψ!(Ψ) + + @inbounds for (F, bubble_functions) in indices.bubbles + for (w, α, _, J) in bubble_functions + for (I_id, I, I_sgn) in indices.components + Ψw[I_id] = I_sgn * _hat_Ψ(r,Vk,α,F,I,J,T) + end + Ψ[w] = Ψw + end + end + nothing +end + +""" + _hat_Ψ(r,::Val{k},α,F,I,J,T)::T + +BarycentricPΛBasis.Ψ matrix elements in the reference simplex, T is the scalar return type + +This is actually not faster than computing the matrices and the minors +explicitely like when vertices are given, but might be usefull in case we want +to compute these at compile time one day. +""" +function _hat_Ψ(r,Vk::Val{k},α,F,I,J,::Type{T})::T where {T,k} + @check sum(α) == r + @check length(I) == length(J) == k + + iszero(k) && return one(T) # 0 forms + + @inbounds begin + + s = Int(isone(J[1])) + n = count(i-> (J[i]-1)∉I, (1+s):k) + + n > 1 && return 0. # rank M_IJ inferior to 2 + + p = _findfirst_val_or_zero(j-> (I[j]+1)∉J, 1, k) + + if isone(n) # rank M_IJ is 1 + m = _findfirst_val_or_zero(i-> (J[i]-1)∉I, (s+1), k) + u_p, v_m = _u(p,F,I), _v(m,α,J,r) + sgn = _minusone_if_even_else_one(m+p+1) + iszero(s) && return sgn*u_p*v_m + + q = _findfirst_val_or_zero(j-> (I[j]+s)∉J, (p+1), k) + u_q = _u(q,F,I) + sgn *= _minusone_if_even_else_one(q+1) + return sgn * v_m * (u_q - u_p) + end + + u, v = _u(F,I,Vk), _v(α,J,r,Vk) + if iszero(s) + return 1 + sum( u .* v ) + else + Ψ_IJ = one(T) + sum_v = v[1] + for l in 1:p-1 + vlp = v[l+1] + sum_v += vlp + Ψ_IJ += vlp*u[l] + end + for l in (p+1):k + vl = v[l] + sum_v += vl + Ψ_IJ += vl*u[l] + end + sgn = _minusone_if_even_else_one(p+1) + return sgn * (Ψ_IJ - u[p]*sum_v) + end + + end + @unreachable +end + +@propagate_inbounds _u(i::Int,F,I) = Int(isone(F[1])) - Int(I[i]+1 in F) +@propagate_inbounds _u(F::Vector{Int},I,Vk) = ntuple(i->_u(i,F,I), Vk) +@propagate_inbounds _v(j::Int,α,J,r) = α[J[j]]/r +@propagate_inbounds _v(α::Vector{Int},J,r,Vk) = ntuple(j->_v(j,α,J,r), Vk) + +# API + +function _evaluate_nd!( + b::BarycentricPΛBasis{D}, x, + ω::AbstractMatrix, i, cB, + ::Val{r}) where {D,r} + + λ = _cart_to_bary(x, _get_cart_to_bary_matrix(b)) + + # _evaluate_nd!(::BernsteinBasisOnSimplex) without set_value + cB[1] = 1 + _downwards_de_Casteljau_nD!(cB,λ,Val(r),Val(D)) + + @inbounds for (_, bubble_functions) in get_bubbles(b) + for (w, _, α_id) in bubble_functions + Ψw = b.Ψ[w] + Bα = cB[α_id] + ω[i,w] = Bα * Ψw + end + end +end + +function _gradient_nd!( + b::BarycentricPΛBasis{D}, x, + ∇ω::AbstractMatrix, i, cB, + ∇B::AbstractMatrix{<:VectorValue{D}}, + s::MVector{D}, + ::Val{r}) where {D,r} + + _gradient_nd!(b.scalar_bernstein_basis, x, ∇B, 1, cB, nothing, s, Val(r)) + + @inbounds for (_, bubble_functions) in get_bubbles(b) + for (w, _, α_id) in bubble_functions + Ψw = b.Ψ[w] + ∇Bα = ∇B[1,α_id] + ∇ω[i,w] = ∇Bα ⊗ Ψw + end + end +end + +function _hessian_nd!( + b::BarycentricPΛBasis{D}, x, + Hω::AbstractMatrix, i, cB, + ::Nothing, + HB::AbstractMatrix{<:TensorValue{D,D}}, + s::MMatrix{D,D}, + ::Val{r}) where {D,r} + + _hessian_nd!(b.scalar_bernstein_basis, x, HB, 1, cB, nothing, nothing, s, Val(r)) + + @inbounds for (_, bubble_functions) in get_bubbles(b) + for (w, _, α_id) in bubble_functions + Ψw = b.Ψ[w] + HBα = HB[1,α_id] + Hω[i,w] = HBα ⊗ Ψw + end + end +end + + +################################################################ +# Combination, Bernstein term and Barycentric PΛ bases helpers # +################################################################ + +# A combination is a set of positive integers sorted in increasing order +# a.k.a an increasing collection of indices in a range 1:D +# F = 1 ≤ F1 < ... < Fd ≤ D +# It is used to represent faces of polytopes (the indices iddentifying the +# vertices of the face) or a component of a k-form as in (1,3) ~ dx¹∧dx³ . + +""" + _sorted_combinations(D,k) + +Return a vector of all the combinations I_i of {1:`D`} of length `k`: + +1 ≤ I\\_1 < ... < I\\_k ≤ `D` + +sorted in right-digit to left-digit lexicographic order, e.g. + +```julia +[ [1,2], [1,3], [2,3] ] # for D=3, k=2\\ +[ [1,2], [1,3], [2,3], [1,4], [2,4], [3,4] ] # for D=4, k=2 +``` + +This example shows that this order of sorted combinations of same length `k` is +independent of the dimension `D`, unlike with the usual (left-digit to right-digit) +lexicographic order where 14 would be smaller than 23. + +So with the chosen order, 23 is always the third length-2 combination, not the `D`ᵗʰ. +""" +function _sorted_combinations(D::Int,k::Int) + iszero(k) && return Vector{Int}[ Int[] ] + comp_rev_perm(tup) = Int[D-tup[k-i+1]+1 for i in 1:k] + inc_perms = combinations(1:D,k) .|> (tup -> comp_rev_perm(tup)) |> reverse + return inc_perms +end + +""" + _combination_index(I) + +Linear index of `I` amongst combinations of the same size `k`, +sorted in right-to-left lexicographic order. It depends on `k` but not on the +space dimension, see [`_sorted_combinations`](@ref). +""" +@inline function _combination_index(combi) + k = length(combi) + return sum( binomial(combi[i]-1, i) for i in 1:k; init=0) + 1 +end + +""" + _complement(I, D) + +Given a k-combination `I` of elements of `1:D`, returns the unique (`D`-k) +combination of `1:D` "`Icomp`" such that `I ∪ Icomp ⊇ 1:D`. +""" +function _complement(combi, D) + k = length(combi) + combi_comp = Vector{Int}(undef, D-k) + curr_perm, curr_comp = 1, 1 + for i in 1:D + if curr_perm ≤ k && combi[curr_perm] == i + curr_perm += 1 + else + combi_comp[curr_comp] = i + curr_comp += 1 + end + end + combi_comp +end + +""" + _combination_sign(I) + +Given a combination `I`, returns the sign of the permutation resulting from +the concatenation of `I` and its complement [`_complement(I)`](@ref _complement). +""" +function _combination_sign(combi) + i, k, acc, delta = 1, 1, 0, 0 + while k <= length(combi) + if combi[k] == i + acc += delta + k += 1 + else + delta += 1 + end + i += 1 + end + return iseven(acc) ? 1 : -1 +end + +""" + _sub_combinations_ids(J) + +Return a vector containing the `k-1` combinations `J\\J[i]` for 1 ≤ i ≤ `k`, +where `k=length(J)`. +""" +function _sub_combinations_ids(combi) + k = length(combi) + sub_combi = MVector{k-1,Int}(undef) + sub_combi_ids = Vector{Int}(undef, k) + for i in 1:k + sub_combi .= ntuple(j -> combi[j + Int(j≥i)],k-1) + sub_combi_id = _combination_index(sub_combi) + sub_combi_ids[i] = sub_combi_id + end + sub_combi_ids +end + +""" + _sup_multi_indices(α) + +Given `α` a Bernstein term of length N, return the [`bernstein_term_id `](@ref) +of the N terms `α`+eᵢ for 1 ≤ i ≤ N in a Vector (in order of i increasing). +""" +function _sup_multi_indices(α) + N = length(α) + sup_α = MVector{N,Int}(undef) + sup_α_ids = Vector{Int}(undef, N) + for i in 1:N + sup_α .= ntuple(k -> α[k]+Int(k==i), N) + sup_α_id = bernstein_term_id(sup_α) + sup_α_ids[i] = sup_α_id + end + return sup_α_ids +end + +""" + _support(α) + +Given a bernstein multi-index `α`, returns the set of indices `i` such that `αᵢ`>0. +""" +function _support(α) + s = Int[] + for (i,αi) in enumerate(α) + if αi > 0 + push!(s, i) + end + end + s +end + +""" + _minor(M,I,J,::Val{k}) + +Computes the minor (determinant of sub-matrix) of the matrix M where the rows of +indices i∈`I` and column of indics j∈`J` are kept. `k` is the length of both `I` +and `J`. +""" +function _minor(M,I,J,::Val{k}) where k + @check length(I) == length(J) + @check I ⊆ axes(M)[1] + @check J ⊆ axes(M)[2] + + T = eltype(M) + m = MMatrix{k,k,T,k*k}(undef) + for (i, Ii) in enumerate(take(I,k)) + for (j, Jj) in enumerate(take(J,k)) + @inbounds m[i,j] = M[Ii,Jj] + end + end + det(m) +end + +@propagate_inbounds function _minusone_if_even_else_one(i) + iseven(i) ? -1 : 1 +end + +@propagate_inbounds function _findfirst_val_or_zero(pred, start, stop) + r = findfirst(pred,start:stop) + return isnothing(r) ? 0 : r+start-1 +end + +@propagate_inbounds function _minimum_or_one(s) + maxint = typemax(Int) + j = minimum(s, init=maxint) + j = (j==maxint) ? 1 : j +end diff --git a/src/Polynomials/BernsteinBases.jl b/src/Polynomials/BernsteinBases.jl new file mode 100644 index 000000000..af5b789df --- /dev/null +++ b/src/Polynomials/BernsteinBases.jl @@ -0,0 +1,898 @@ +""" + Bernstein <: Polynomial + +Type representing Bernstein polynomials, c.f. [Bernstein polynomials](@ref) section. +""" +struct Bernstein <: Polynomial end + +isHierarchical(::Type{Bernstein}) = false + + +##################################### +# Cartesian product Bernstein bases # +##################################### + +""" + BernsteinBasis{D,V} = CartProdPolyBasis{D,V,Bernstein} + +Alias for cartesian product of a scalar tensor 1D-Bernstein basis, scalar valued +or multivalued. +""" +const BernsteinBasis{D,V} = CartProdPolyBasis{D,V,Bernstein} + +""" + BernsteinBasis(::Val{D}, ::Type{V}, order::Int, terms::Vector) + BernsteinBasis(::Val{D}, ::Type{V}, order::Int [, filter::Function]) + BernsteinBasis(::Val{D}, ::Type{V}, orders::Tuple [, filter::Function]) + +High level constructors of [`BernsteinBasis`](@ref). +""" +BernsteinBasis(args...) = CartProdPolyBasis(Bernstein, args...) + + +################################ +# 1D evaluation implementation # +################################ + +@inline function _de_Casteljau_step_1D!(v,d,i,λ1,λ2) + # i = k+1 + + # vₖ <- xvₖ₋₁ # Bᵏₖ(x) = x*Bᵏ⁻¹ₖ₋₁(x) + v[d,i] = λ2*v[d,i-1] + # vⱼ <- xvⱼ₋₁ + (1-x)vⱼ # Bᵏⱼ(x) = x*Bᵏ⁻¹ⱼ₋₁(x) + (1-x)*Bᵏ⁻¹ⱼ(x) for j = k-1, k-2, ..., 1 + for l in i-1:-1:2 + v[d,l] = λ2*v[d,l-1] + λ1*v[d,l] + end + # v₀ <- (1-x)v₀ # Bᵏ₀(x) = (1-x)*Bᵏ⁻¹₀(x) + v[d,1] = λ1*v[d,1] + nothing +end + +# jth Bernstein poly of order K at x: +# Bᵏⱼ(x) = binom(K,j) * x^j * (1-x)^(K-j) = x*Bᵏ⁻¹ⱼ₋₁(x) + (1-x)*Bᵏ⁻¹ⱼ(x) +function _evaluate_1d!(::Type{Bernstein},K::Int,v::AbstractMatrix{T},x,d) where T<:Number + @inbounds begin + if iszero(K) + v[d,1] = one(T) + return + end + + n = K + 1 # n > 1 + λ2 = x[d] + λ1 = one(T) - λ2 + + # In place De Casteljau: init with B¹₀(x)=x and B¹₁(x)=1-x + v[d,1] = λ1 + v[d,2] = λ2 + + for i in 3:n + _de_Casteljau_step_1D!(v,d,i,λ1,λ2) + end + end + # still optimisable for K > 2/3: + # - compute bj = binomials(k,j) ∀j at compile time + # - compute vj = xʲ*(1-x)ᴷ⁻ʲ recursively in place like De Casteljau (saving half the redundant multiplications) + # - do it in a stack allocated cache (MVector, Bumber.jl) + # - @simd affect bj * vj in v[d,i] for all j +end + +# First derivative of the jth Bernstein poly of order K at x: +# (Bᵏⱼ)'(x) = K * ( Bᵏ⁻¹ⱼ₋₁(x) - Bᵏ⁻¹ⱼ(x) ) +# = K * x^(j-1) * (1-x)^(K-j-1) * ((1-x)*binom(K-1,j-1) - x*binom(K-1,j)) +function _gradient_1d!(::Type{Bernstein},K::Int,g::AbstractMatrix{T},x,d) where T<:Number + @inbounds begin + + if K<2 # base cases + z = zero(T) + o = one(T) + K==0 && (g[d,1] = z) + K==1 && (g[d,1] =-o; g[d,2] = o) + return + end + + n = K + 1 # n > 2 + + # De Casteljau for Bᵏ⁻¹ⱼ for j = k-1, k-2, ..., 1 + _evaluate_1d!(Bernstein,K-1,g,x,d) + + # gₖ <- K*gₖ₋₁ # ∂ₓBᵏₖ(x) = K*Bᵏ⁻¹ₖ₋₁(x) + g[d,n] = K*g[d,n-1] + # gⱼ <- K(gⱼ₋₁ + gⱼ) # ∂ₓBᵏⱼ(x) = K(Bᵏ⁻¹ⱼ₋₁(x) - Bᵏ⁻¹ⱼ(x)) for j = k-1, k-2, ..., 1 + for l in n-1:-1:2 + g[d,l] = K*(g[d,l-1] - g[d,l]) + end + # g₀ <- K*g₀ # ∂ₓBᵏ₀(x) = -K*Bᵏ⁻¹₀(x) + g[d,1] = -K*g[d,1] + end +end + + +# Second derivative of the jth Bernstein poly of order K at x: +# (Bᵏⱼ)''(x) = K(K-1) * ( Bᵏ⁻²ⱼ₋₂(x) -2*Bᵏ⁻²ⱼ₋₁(x) + Bᵏ⁻²ⱼ(x) ) +# = K(K-1) * x^(j-2) * (1-x)^(K-j-2) * ( (1-x)^2*binom(K-2,j-2) +# - 2x*(1-x)*binom(K-2,j-1) + (x)^2*binom(K-2,j) +# ) +function _hessian_1d!(::Type{Bernstein},K::Int,h::AbstractMatrix{T},x,d) where T<:Number + @inbounds begin + + if K<3 # base cases + z = zero(T) + o = one(T) + K==0 && (h[d,1] = z) + K==1 && (h[d,1] = z; h[d,2] = z) + K==2 && (h[d,1] = 2o; h[d,2] = -4o; h[d,3] = 2o) + return + end + + n = K + 1 # n > 3 + KK = K*(K-1) + + # De Casteljau for Bᵏ⁻²ⱼ for j = k-2, k-3, ..., 1 + _evaluate_1d!(Bernstein,K-2,h,x,d) + + # hₖ <- K(K-1)*hₖ₋₂ + h[d,n] = KK*h[d,n-2] + # hₖ₋₁ <- K(K-1)*(-2*hₖ₋₁ + hₖ₋₂) + h[d,n-1] = KK*( h[d,n-3] -2*h[d,n-2] ) + + # hⱼ <- K(K-1)(hⱼ₋₂ -2hⱼ₋₁ + hⱼ) + for l in n-2:-1:3 + h[d,l] = KK*( h[d,l-2] -2*h[d,l-1] + h[d,l] ) + end + + # h₁ <- K(K-1)*(-2h₀ + h₁) + h[d,2] = KK*( -2*h[d,1] + h[d,2] ) + # h₀ <- K(K-1)*h₀ + h[d,1] = KK*h[d,1] + end +end + +function _derivatives_1d!(::Type{Bernstein},K,t::NTuple{2},x,d) + if K < 2 + @inline _evaluate_1d!(Bernstein, K, t[1], x, d) + @inline _gradient_1d!(Bernstein, K, t[2], x, d) + return + end + + @inbounds begin + n = K + 1 # n > 2 + v, g = t + + λ2 = x[d] + λ1 = one(eltype(v)) - λ2 + + # De Casteljau for Bᵏ⁻¹ⱼ for j = k-1, k-2, ..., 1 + _evaluate_1d!(Bernstein,K-1,v,x,d) + + # Compute gradients as _gradient_1d! + g[d,n] = K*v[d,n-1] + @simd for l in n-1:-1:2 + g[d,l] = K*(v[d,l-1] - v[d,l]) + end + g[d,1] = -K*v[d,1] + + # Last step of De Casteljau for _evaluate_1d! + _de_Casteljau_step_1D!(v,d,n,λ1,λ2) + end +end + +function _derivatives_1d!(::Type{Bernstein},K,t::NTuple{3},x,d) + if K < 3 + @inline _evaluate_1d!(Bernstein, K, t[1], x, d) + @inline _gradient_1d!(Bernstein, K, t[2], x, d) + @inline _hessian_1d!( Bernstein, K, t[3], x, d) + return + end + + @inbounds begin + n = K + 1 # n > 3 + v, g, h = t + + λ2 = x[d] + λ1 = one(eltype(v)) - λ2 + + # De Casteljau until Bᵏ⁻²ⱼ ∀j + _evaluate_1d!(Bernstein,K-2,v,x,d) + + # Compute hessians as in _hessian_1d! + KK = K*(K-1) + h[d,n] = KK*v[d,n-2] + h[d,n-1] = KK*( v[d,n-3] -2*v[d,n-2] ) + @simd for l in n-2:-1:3 + h[d,l] = KK*( v[d,l-2] -2*v[d,l-1] + v[d,l] ) + end + h[d,2] = KK*( -2*v[d,1] + v[d,2] ) + h[d,1] = KK*v[d,1] + + # One step of De Casteljau to get Bᵏ⁻¹ⱼ ∀j + _de_Casteljau_step_1D!(v,d,n-1,λ1,λ2) + + # Compute gradients as in _gradient_1d! + g[d,n] = K*v[d,n-1] + @simd for l in n-1:-1:2 + g[d,l] = K*(v[d,l-1] - v[d,l]) + end + g[d,1] = -K*v[d,1] + + # Last step of De Casteljau for _evaluate_1d! + _de_Casteljau_step_1D!(v,d,n,λ1,λ2) + end +end + + +################################################### +# Bernstein bases on simplices using de Casteljau # +################################################### + +""" + BernsteinBasisOnSimplex{D,V,M} <: PolynomialBasis{D,V,Bernstein} + +Type for the multivariate Bernstein basis in barycentric coordinates, +c.f. [Bernstein polynomials](@ref) section of the documentation. If `V` is not +scalar, a Cartesian product of the Bernstein scalar basis is made for each +independent component of `V`. + +The index of `B_α` in the basis is [`bernstein_term_id(α)`](@ref bernstein_term_id). + +`M` is Nothing for the reference tetrahedra barycentric coordinates or +`SMatrix{D+1,D+1}` if some simplex (triangle, tetrahedra, ...) vertices +coordinates are given. +""" +struct BernsteinBasisOnSimplex{D,V,M} <: PolynomialBasis{D,V,Bernstein} + max_order::Int + cart_to_bary_matrix::M # Nothing or SMatrix{D+1,D+1} + + function BernsteinBasisOnSimplex{D}(::Type{V},order::Int,vertices=nothing) where {D,V} + _simplex_vertices_checks(Val(D), vertices) + + K = Int(order) + cart_to_bary_matrix = _compute_cart_to_bary_matrix(vertices, Val(D+1)) + M = typeof(cart_to_bary_matrix) # Nothing or SMatrix + new{D,V,M}(K,cart_to_bary_matrix) + end +end + +function _simplex_vertices_checks(::Val{D}, vertices) where D + if !isnothing(vertices) + @check length(vertices) == D+1 "$D+1 vertices are required to define a $D-dim simplex, got $(length(vertices))" + @check eltype(vertices) <: Point{D} "Vertices should be of type <:Point{$D}, got $(eltype(vertices))" + end +end + +""" + BernsteinBasisOnSimplex(::Val{D},::Type{V},order::Int) + BernsteinBasisOnSimplex(::Val{D},::Type{V},order::Int,vertices) + +Constructors for [`BernsteinBasisOnSimplex`](@ref). + +If specified, `vertices` is a collection of `D+1` `Point{D}` defining a simplex +used to compute the barycentric coordinates from, it must be non-degenerated +(have nonzero volume). +""" +function BernsteinBasisOnSimplex(::Val{D},::Type{V},order::Int,vertices=nothing) where {D,V} + BernsteinBasisOnSimplex{D}(V,order,vertices) +end + +Base.size(b::BernsteinBasisOnSimplex{D,V}) where {D,V} = (num_indep_components(V)*binomial(D+get_order(b),D),) +get_order(b::BernsteinBasisOnSimplex) = b.max_order +get_orders(b::BernsteinBasisOnSimplex{D}) where D = tfill(get_order(b), Val(D)) + +function testvalue(::Type{BernsteinBasisOnSimplex{D,V,M}}) where {D,V,M} + if M == Nothing + vertices = nothing + else + Pt = Point{D,eltype(M)} + vertices = ntuple( j -> Pt( ntuple( i -> j==i+1, Val(D)) ), Val(D+1)) + end + BernsteinBasisOnSimplex{D}(V,0,vertices) +end + + +##################### +# Bernstein Helpers # +##################### + +""" + _compute_cart_to_bary_matrix(vertices, ::Val{N}) + _compute_cart_to_bary_matrix(::Nothing,::Val) = nothing + +For the given the vertices of a `D`-simplex (`D` = `N`-1), computes the change +of coordinate matrix `x_to_λ` from cartesian to barycentric, such that +`λ` = `x_to_λ` * `x` with `sum(λ) == 1` and `x == sum(λ .* vertices)`. +""" +function _compute_cart_to_bary_matrix(vertices, ::Val{N}) where N + T = eltype(eltype(vertices)) + λ_to_x = MMatrix{N,N,T}(undef) + for (i,v) in enumerate(vertices) + λ_to_x[:,i] .= tuple(one(T), v...) + end + + x_to_λ = inv(λ_to_x) # Doesn't throw if singular because this is a StaticArrays Matrix + msg = "The simplex defined by the given vertices is degenerated (is flat / has zero volume)." + !all(isfinite, x_to_λ) && throw(DomainError(vertices,msg)) + + return SMatrix{N,N,T}(x_to_λ) +end +_compute_cart_to_bary_matrix(::Nothing, ::Val) = nothing + +""" + _cart_to_bary(x::Point{D,T}, ::Nothing) + +Compute the barycentric coordinates with respect to the reference simplex of the +given cartesian coordinates `x`, that is `λ`=(x1, ..., xD, 1-x1-x2-...-xD). +""" +@inline function _cart_to_bary(x::Point{D,T}, ::Nothing) where {D,T} + sum_x = sum(x,init=zero(T)) + return SVector(1-sum_x, x...) +end + +""" + _cart_to_bary(x::Point{D,T}, x_to_λ) + +Compute the barycentric coordinates of the given cartesian coordinates `x` using +the `x_to_λ` change of coordinate matrix, see [`_compute_cart_to_bary_matrix`](@ref). +""" +@inline function _cart_to_bary(x::Point{D,T}, x_to_λ) where {D,T} + x_1 = SVector{D+1,T}(one(T), x...) + return x_to_λ*x_1 +end + +""" + bernstein_terms(K,D) + +Return the vector of multi-indices for the `D`-dimensional Bernstein basis of +order `K`, that is + +``Iₖᴰ = { α ∈ {0:K}ᴰ⁺¹ | |α| = K }`` + +ordered in decreasing lexicographic order, e.g. {200, 110, 101, 020, 011, 002} +for K=2, D=2. +""" +function bernstein_terms(K,D) + terms = collect(multiexponents(D+1,K)) + terms = convert(Vector{Vector{Int}}, terms) +end + + +######################## +# de Casteljau helpers # +######################## + +""" + bernstein_term_id(α) + +For a given Bernstein multi-index `α` (vector or tuple), return the associated +linear index of `α` ordered in decreasing lexicographic order, that is the `i` +such that + + (i,α) ∈ enumerate(bernstein_terms(K,D)) + +where K = sum(`α`), see also [`bernstein_terms`](@ref). +""" +function bernstein_term_id(α) + D = length(α)-1 + @check D ≥ 0 + @inbounds i = sum(_L_slices_size(L, D, _L_slice_2(L,α,D)) for L in 1:D; init=0) + 1 + return i +end + + +# For a given Bernstein term `α`, return the index (starting from 0) of the +# (D-`L`)-slice to which `α` belongs within the (D-`L`-1)-slice of +# the D-multiexponent simplex (D = `N`-1). +# +# In a D-multiexponent simplex of elements `α`, ordered in a vector in +# decreasing lexicographic order, the (D-`L`)-slices are the consecutive `α`s +# having iddentical first `L` indices `α`. +""" + _L_slice(L, α, D) + +where `L` ∈ 1:N, returns `sum( α[i] for i in (L+1):(D+1) )`. +""" +Base.@propagate_inbounds _L_slice_2(L, α, D) = sum( α[i] for i in (L+1):(D+1) ) + +# Return the length of the `l`-1 first (`D`-`L`)-slices in the +# `D`-multiexponent simplex (ordered in decreasing lexicographic order). +# Those numbers are the "(`D`-`L`)-simplex numbers". +""" + _L_slices_size(L,D,l) = binomial(D-L+l, D-L+1) +""" +_L_slices_size(L,D,l) = binomial(D-L+l, D-L+1) + +""" + _sub_multi_indices!(sub_ids, α) + +Given a positive multi-index `α`, sets in place in `sub_ids` the couples +(`id`, `d`) with `d` in 1:`N` for which the multi-index `αd⁻` = `α`-e`d` is +positive (that is `α`[`d`]>0), and `id` is the linear index of `αd⁻` +(see [`bernstein_term_id`](@ref)). + +The function returns the number of sub indices set. +""" +function _sub_multi_indices!(sub_ids, α, ::Val{N}) where N + @check length(sub_ids) >= N + nb_sα = 0 + for i in 1:N + α⁻ = ntuple(k -> α[k]-Int(k==i), Val(N)) + if all(α⁻ .≥ 0) + nb_sα += 1 + id⁻ = bernstein_term_id(α⁻) + sub_ids[nb_sα] = (id⁻, i) + end + end + return nb_sα +end + +""" + _sub_sub_multi_indices!(sub_ids, α, ::Val{N}) + +Like [`_sub_multi_indices`](@ref), but sets the triples (`id`, `t`, `q`) in `sub_ids`, +with `t,q` in 1:`N` for which the multi-index `αd⁻⁻` = `α`-e`t`-e`q` is positive, +and returns the number of triples set. +""" +function _sub_sub_multi_indices!(sub_ids, α, ::Val{N}) where N + @check length(sub_ids) >= binomial(N+1,2) + nb_ssα = 0 + for i in 1:N + for j in i:N + α⁻⁻ = ntuple(k -> α[k]-Int(k==i)-Int(k==j), Val(N)) + if all(α⁻⁻ .≥ 0) + nb_ssα += 1 + id⁻⁻ = bernstein_term_id(α⁻⁻) + sub_ids[nb_ssα] = (id⁻⁻, i, j) + end + end + end + return nb_ssα +end + +""" + _sup_multi_indices!(sup_ids, α, ::Val{N}) + +Like [`_sub_multi_indices!`](@ref), but sets the indices for the `N` multi-indices +`αd⁺` = `α`+e`d` for 1≤d≤`N`, and returns `N` +""" +function _sup_multi_indices!(sup_ids, α, ::Val{N}) where N + @check length(sup_ids) >= N + for i in 1:N + α⁺ = ntuple(k -> α[k]+Int(k==i), Val(N)) + id⁺ = bernstein_term_id(α⁺) + sup_ids[i] = (id⁺, i) + end + return N +end + + +################################ +# nD evaluation implementation # +################################ + +# Overload _return_cache and _setsize for in place D-dimensional de Casteljau algorithm +function _return_cache( + b::BernsteinBasisOnSimplex{D}, x,::Type{G},::Val{N_deriv}) where {D,G,N_deriv} + + T = eltype(G) + K = get_order(b) + np = length(x) + ndof = length(b) + ndof_scalar = binomial(K+D,D) + + r = CachedArray(zeros(G,(np,ndof))) + s = MArray{Tuple{Vararg{D,N_deriv}},T}(undef) + c = CachedVector(zeros(T,ndof_scalar)) + # The cache c here holds all scalar nD-Bernstein polynomials, no other caches needed for derivatives + t = ntuple( _ -> nothing, Val(N_deriv)) + (r, s, c, t...) +end + +function _setsize!(b::BernsteinBasisOnSimplex{D}, np, r, t...) where D + K = get_order(b) + ndof = length(b) + ndof_scalar = binomial(K+D,D) + setsize!(r,(np,ndof)) + setsize!(t[1],(ndof_scalar,)) +end + +function _evaluate_nd!( + b::BernsteinBasisOnSimplex{D,V}, x, + r::AbstractMatrix, i, + c::AbstractVector{T}, VK::Val) where {D,V,T} + + λ = _cart_to_bary(x, b.cart_to_bary_matrix) + c[1] = one(T) + _downwards_de_Casteljau_nD!(c,λ,VK,Val(D)) + + k = 1 + for s in c + k = _cartprod_set_value!(r,i,s,k) + end +end + +function _gradient_nd!( + b::BernsteinBasisOnSimplex{D,V}, x, + r::AbstractMatrix{G}, i, + c::AbstractVector{T}, + g::Nothing, + s::MVector{D,T}, ::Val{K}) where {D,V,G,T,K} + + x_to_λ = b.cart_to_bary_matrix + λ = _cart_to_bary(x, x_to_λ) + + c[1] = one(T) + _downwards_de_Casteljau_nD!(c,λ,Val(K-1),Val(D)) + + _grad_Bα_from_Bαm!(r,i,c,s,Val(K),Val(D),V,x_to_λ) +end + +function _hessian_nd!( + b::BernsteinBasisOnSimplex{D,V}, x, + r::AbstractMatrix{G}, i, + c::AbstractVector{T}, + g::Nothing, + h::Nothing, + s::MMatrix{D,D,T}, ::Val{K}) where {D,V,G,T,K} + + x_to_λ = b.cart_to_bary_matrix + λ = _cart_to_bary(x, x_to_λ) + + c[1] = one(T) + _downwards_de_Casteljau_nD!(c,λ,Val(K-2),Val(D)) + + _hess_Bα_from_Bαmm!(r,i,c,s,Val(K),Val(D),V,x_to_λ) +end + +# @generated functions as otherwise the time for computing the indices are the bottlneck... +@doc """ + _downwards_de_Casteljau_nD!(c, λ,::Val{K},::Val{D},::Val{K0}=Val(1)) + +Iteratively applies de Casteljau algorithm in reverse in place using `λ`s as +coefficients. + +If `K0 = 1`, `λ` are the barycentric coordinates of some point `x` and `c[1] = 1`, +this computes all order `K` basis Bernstein polynomials at `x`: + +`c[α_id] = B_α(x)  ∀α ∈ bernstein_terms(K,D)` + +where `α_id` = [`bernstein_term_id`](@ref)(α). +""" +@generated function _downwards_de_Casteljau_nD!(c, λ,::Val{K},::Val{D},::Val{K0}=Val(1)) where {K,D,K0} + z = zero(eltype(c)) + ex_v = Vector{Expr}() + sub_ids = MVector{D+1,Tuple{Int,Int}}(undef) + + for Ki in K0:K + # Iterations are in reverse lexicographic order (left to right), because α-ei is + # always stored on the left of α (as α-ei < α in lexicographic order), so the + # erased B_β replaced by B_α won't be used to compute the remainings B_γ for |γ|=`K` + # with γ>α in lexicographic order. + terms = bernstein_terms(Ki,D) + for (id,α) in Iterators.reverse(enumerate(terms)) # For all |α| = Ki + # s = 0. + push!(ex_v, :(s = $z)) + + # For all |β| = |α|-1; β ≥ 0 + nb_sα = _sub_multi_indices!(sub_ids, α, Val(D+1)) + for (id_β, d) in take(sub_ids, nb_sα) + # s += λ_d * B_β + push!(ex_v, :(@inbounds s += λ[$d]*c[$id_β])) + end + + # c[id] = B_α + push!(ex_v, :(@inbounds c[$id] = s)) + end + end + return Expr(:block, ex_v...) +end + +""" + _de_Casteljau_nD!(c, λ,::Val{K},::Val{D},::Val{Kf}=Val(0)) + +Iteratively applies de Casteljau algorithm in place using `λ`s as +coefficients. + +If `Kf = 0`, `λ` are the barycentric coordinates of some point `x` and `c` contains +the Bernstein coefficients ``c_α`` of a polynomial ``p`` (that is ``p(x) = ∑_α c_α B_α(x)`` for +``α`` in [`bernstein_terms`](@ref)(`K`,`D`) ), this computes + +``c[1] = p(x)`` + +where the ``c_α`` must be initially stored in `c`[`α_id`], where +`α_id` = [`bernstein_term_id`](@ref)(α). +""" +function _de_Casteljau_nD! end # + +@generated function _de_Casteljau_nD!(c, λ,::Val{K},::Val{D},::Val{Kf}=Val(0)) where {K,D,Kf} + z = zero(eltype(c)) + ex_v = Vector{Expr}() + sup_ids = MVector{D+1,Tuple{Int,Int}}(undef) + + for Ki in (K-1):-1:Kf + # Iterations are in lexicographic order (right to left), because α+ei is + # always stored on the right of α (as α+ei > α in lexicographic order), so the + # erased B_β replaced by B_α won't be used to compute the remainings B_γ for |γ|=`K` + # with γ<α in lexicographic order. + terms = bernstein_terms(Ki,D) + for (id,α) in enumerate(terms) # For all |α| = Ki + # s = 0. + push!(ex_v, :(s = $z)) + + # For all |β| = |α|+1 + _sup_multi_indices!(sup_ids, α, Val(D+1)) + for (id_β, d) in sup_ids + # s += λ_d * B_β + push!(ex_v, :(@inbounds s += λ[$d]*c[$id_β])) + end + + # c[id] = B_α (= s) + push!(ex_v, :(@inbounds c[$id] = s)) + end + end + return Expr(:block, ex_v...) +end + + +# ∂q(B_α) = K ∑_{1 ≤ i ≤ N} ∂q(λi) B_β +# for 1 ≤ q ≤ D and β = α-ei +@generated function _grad_Bα_from_Bαm!( + r,i,c,s,::Val{K},::Val{D},::Type{V},x_to_λ=nothing) where {K,D,V} + + ex_v = Vector{Expr}() + ncomp = num_indep_components(V) + z = zero(eltype(c)) + δ(i,j) = Int(i==j) + sub_ids = MVector{D+1,Tuple{Int,Int}}(undef) + + for (id_α,α) in enumerate(bernstein_terms(K,D)) + push!(ex_v, :(@inbounds s .= $z)) # s = 0 + + nb_sα = _sub_multi_indices!(sub_ids, α, Val(D+1)) + for (id_β, i) in take(sub_ids, nb_sα) # β = α - ei + push!(ex_v, :(@inbounds B_β = c[$id_β])) + # s[q] = Σ_β ∂q(λi) B_β + for q in 1:D + if x_to_λ == Nothing + # ∇λ(eq)_i = δ_{q+1,i} - δ_1i + Cqi = δ(i,q+1) - δ(1,i) + iszero(Cqi) || push!(ex_v, :(@inbounds s[$q] += $Cqi*B_β)) + else + # ∂q(λi) = ei (x_to_λ*(e1 - e{q+1}) - x_to_λ*(e1)) = ei * x_to_λ * e{q+1} + # ∂q(λi) = x_to_λ[i,q+1] + push!(ex_v, :(@inbounds s[$q] += x_to_λ[$i,$(q+1)]*B_β)) + end + end + end + push!(ex_v, :(@inbounds s .*= $K)) # s = Ks. + + k = ncomp*(id_α-1) + 1 + push!(ex_v, :(_cartprod_set_derivative!(r,i,s,$k,V))) + end + + return Expr(:block, ex_v...) +end + +# ∂t(∂q(B_α)) = K(K-1) ∑_{1 ≤ i,j ≤ N} ∂t(λj) ∂q(λi) B_β +# for 1 ≤ t,q ≤ D and β = α - ei - ej +@generated function _hess_Bα_from_Bαmm!( + r,i,c,s,::Val{K},::Val{D},::Type{V},x_to_λ=nothing) where {K,D,V} + + ex_v = Vector{Expr}() + ncomp = num_indep_components(V) + z = zero(eltype(c)) + δ(i,j) = Int(i==j) + C(q,t,i,j) = (δ(i,q+1)-δ(i,1))*(δ(j,t+1)-δ(j,1)) + N_max_ssα = binomial(D+2,2) + sub_sub_α_ids = MVector{N_max_ssα, NTuple{3,Int}}(undef) + + for (id_α,α) in enumerate(bernstein_terms(K,D)) + push!(ex_v, :(@inbounds s .= $z)) # s = 0 + + nb_subsub = _sub_sub_multi_indices!(sub_sub_α_ids, α, Val(D+1)) + for (id_β, i, j) in take(sub_sub_α_ids, nb_subsub) + + push!(ex_v, :(@inbounds B_β = c[$id_β])) + for t in 1:D + for q in 1:D + if x_to_λ == Nothing + # s[t,q] = Σ_β B_β (δ_iq - δ_iN)*(δ_jt - δ_jN) + Cβ = C(q,t,i,j) + if i≠j Cβ += C(q,t,j,i) end + iszero(Cβ) || push!(ex_v, :(@inbounds s[$t,$q] += $Cβ*B_β)) + else + push!(ex_v, :(@inbounds C = x_to_λ[$i,$(q+1)]*x_to_λ[$j,$(t+1)])) + if i≠j push!(ex_v, :(@inbounds C += x_to_λ[$j,$(q+1)]*x_to_λ[$i,$(t+1)])) end + push!(ex_v, :(@inbounds s[$t,$q] += C*B_β)) + end + end + end + end + push!(ex_v, :(@inbounds s .*= $(K*(K-1))) ) # s = K(K-1)s + + k = ncomp*(id_α-1) + 1 + push!(ex_v, :(_cartprod_set_derivative!(r,i,s,$k,V))) + end + + return Expr(:block, ex_v...) +end + + +####################################################### +#### Bernstein bases on simplices Naive implementation# +####################################################### +# +# """ +# BernsteinBasisOnSimplex{D,V} <: PolynomialBasis{D,V,Bernstein} +# +# This basis uses barycentric coordinates defined by the vertices of the +# reference `D`-simplex. +# """ +# struct BernsteinBasisOnSimplex{D,V} <: PolynomialBasis{D,V,Bernstein} +# function BernsteinBasisOnSimplex{D}(::Type{V},order::Int) where {D,V} +# K = Int(order) +# new{D,V}() +# end +# end +# +# function BernsteinBasisOnSimplex(::Val{D},::Type{V},order::Int) where {D,V} +# BernsteinBasisOnSimplex{D}(V,order) +# end +# +# Base.size(::BernsteinBasisOnSimplex{D,V}) where {D,V} = (num_indep_components(V)*binomial(D+K,D),) +# get_exponents(::BernsteinBasisOnSimplex{D,V}) where {D,V} = bernstein_terms(K,D) +# +# ################################ +# # nD evaluation implementation # +# ################################ +# +# # Overload _return_cache and _setsize to add +1 coordinate cache in t +# function _return_cache( +# f::BernsteinBasisOnSimplex{D}, x,::Type{G},::Val{N_deriv}) where {D,G,N_deriv} +# +# T = eltype(G) +# np = length(x) +# ndof = length(f) +# ndof_1d = get_order(f) + 1 +# r = CachedArray(zeros(G,(np,ndof))) +# s = MArray{Tuple{Vararg{D,N_deriv}},T}(undef) +# bernstein_D = D+1 # There are D+1 barycentric coordinates +# t = ntuple( _ -> CachedArray(zeros(T,(bernstein_D,ndof_1d))), Val(N_deriv+1)) +# (r, s, t...) +# end +# function _setsize!(f::BernsteinBasisOnSimplex{D}, np, r, t...) where D +# ndof = length(f) +# ndof_1d = get_order(f) + 1 +# setsize!(r,(np,ndof)) +# bernstein_D = D+1 # There are D+1 barycentric coordinates +# for c in t +# setsize!(c,(bernstein_D,ndof_1d)) +# end +# end +# +# +# function _evaluate_nd!( +# b::BernsteinBasisOnSimplex{D,V}, x, +# r::AbstractMatrix{V}, i, +# c::AbstractMatrix{T}) where {D,V,T} +# +# K = get_order(b) +# terms = _get_terms(b) +# coefs = multinoms(Val(K),Val(D)) +# +# λ = _cart_to_bary(x,nothing) +# +# for d in 1:(D+1) +# _evaluate_1d!(Monomial,Val(K),c,λ,d) # compute powers 0:K of all bary. coords. +# end +# +# k = 1 +# for (ci,m) in zip(terms,coefs) +# +# for d in 1:(D+1) +# @inbounds m *= c[d,ci[d]] +# end +# +# k = _cartprod_set_value!(r,i,m,k) +# end +# end +# +# function _gradient_nd!( +# b::BernsteinBasisOnSimplex{D,V}, x, +# r::AbstractMatrix{G}, i, +# c::AbstractMatrix{T}, +# g::AbstractMatrix{T}, +# s::MVector{D,T}) where {D,V,G,T} +# +# K = get_order(b) +# N = D+1 +# terms = _get_terms(b) +# coefs = multinoms(Val(K),Val(D)) +# +# λ = _cart_to_bary(x,nothing) +# +# for d in 1:N +# _derivatives_1d!(Monomial,Val(K),(c,g),λ,d) +# end +# +# k = 1 +# @inbounds for (ci,m) in zip(terms,coefs) +# +# for i in eachindex(s) +# s[i] = m +# end +# +# for q in 1:D +# for d in 1:D +# if d != q +# s[q] *= c[d,ci[d]] +# else +# s[q] *= g[q,ci[q]]*c[N,ci[N]] - g[N,ci[N]]*c[q,ci[q]] +# end +# end +# end +# +# k = _cartprod_set_derivative!(r,i,s,k,V) +# end +# end +# +# function _hessian_nd!( +# b::BernsteinBasisOnSimplex{D,V}, x, +# r::AbstractMatrix{G}, i, +# c::AbstractMatrix{T}, +# g::AbstractMatrix{T}, +# h::AbstractMatrix{T}, +# s::MMatrix{D,D,T}) where {D,V,G,T} +# +# N = D+1 +# terms = _get_terms(b) +# coefs = multinoms(Val(K),Val(D)) +# +# λ = _cart_to_bary(x,nothing) +# +# for d in 1:N +# _derivatives_1d!(Monomial,Val(K),(c,g,h),λ,d) +# end +# +# k = 1 +# @inbounds for (ci,m) in zip(terms,coefs) +# +# for i in eachindex(s) +# s[i] = m +# end +# +# for t in 1:D +# for q in 1:D +# for d in 1:D +# if d != q && d != t +# # if q == t, D-1 factors +# # else, D-2 factors +# s[t,q] *= c[d,ci[d]] +# elseif q == t # == d +# # +2 factors -> D+1 +# s[t,q] *= (h[d,ci[d]]*c[N,ci[N]] -2g[d,ci[d]]*g[N,ci[N]] + c[d,ci[d]]*h[N,ci[N]]) +# elseif d == q # q ≠ t, we multiply once with the factors with q and t derivative terms +# # +3 factors -> D+1 +# s[t,q] *=( g[t,ci[t]]*g[q,ci[q]]*c[N,ci[N]] +# - g[t,ci[t]]*c[q,ci[q]]*g[N,ci[N]] +# - c[t,ci[t]]*g[q,ci[q]]*g[N,ci[N]] +# + c[t,ci[t]]*c[q,ci[q]]*h[N,ci[N]]) +# end +# end +# end +# end +# +# k = _cartprod_set_derivative!(r,i,s,k,V) +# end +# end +# +# """ +# multinoms(::Val{K}, ::Val{D}) +# +# Returns the tuple of multinomial coefficients for each term in +# [`bernstein_terms`](@ref)(`K`,`D`). For e.g. a term `t`, the +# multinomial can be computed by `factorial(sum(t)) ÷ prod(factorial.(t)` +# """ +# @generated function multinoms(::Val{K},::Val{D}) where {K,D} +# terms = bernstein_terms(K,D) +# multinomials = tuple( (multinomial(α...) for α in terms)... ) +# Meta.parse("return $multinomials") +# end +# diff --git a/src/Polynomials/CartProdPolyBases.jl b/src/Polynomials/CartProdPolyBases.jl new file mode 100644 index 000000000..293533c51 --- /dev/null +++ b/src/Polynomials/CartProdPolyBases.jl @@ -0,0 +1,365 @@ +################################# +# Tensorial nD polynomial bases # +################################# + +""" + struct CartProdPolyBasis{D,V,PT} <: PolynomialBasis{D,V,PT} + +"Cartesian product of a scalar tensor polynomial basis" + +Type representing a basis of a (an)isotropic `D`-multivariate `V`-valued +cartesian product polynomial space + +`V`(𝕊, ∅, ..., ∅) ⊕ `V`(∅, 𝕊, ∅, ..., ∅) ⊕ ... ⊕ `V`(∅, ..., ∅, 𝕊) + +where the scalar space 𝕊 is a (subspace of a) tensor product space of an +univariate polynomial basis. + +The scalar polynomial basis spanning 𝕊 is defined as + + { x ⟶ bα`ᴷ`(x) = bα₁`ᴷ`(x₁) × bα₂`ᴷ`(x₂) × ... × bα`Dᴷ`(x`D`) | α ∈ `terms` } + +where bαᵢ`ᴷ`(xᵢ) is the αᵢth 1D basis polynomial of the basis `PT` of order `K` +evaluated at xᵢ (iᵗʰ comp. of x), and where α = (α₁, α₂, ..., α`D`) is a +multi-index in `terms`, a subset of {0:`K`}`ᴰ`. `terms` is a field that can be +passed in a constructor. + +This type fully implements the [`Field`](@ref) interface, with up to second +order derivatives. +""" +struct CartProdPolyBasis{D,V,PT} <: PolynomialBasis{D,V,PT} + max_order::Int + orders::NTuple{D,Int} + terms::Vector{CartesianIndex{D}} + + function CartProdPolyBasis{D}( + ::Type{PT}, + ::Type{V}, + orders::NTuple{D,Int}, + terms::Vector{CartesianIndex{D}}) where {D,V,PT<:Polynomial} + + @check isconcretetype(PT) "PT needs to be a concrete <:Polynomial type" + + K = maximum(orders; init=0) + msg = "Some term contain a higher index than the maximum degree + 1." + @check all( term -> (maximum(Tuple(term), init=0) <= K+1), terms) msg + new{D,V,PT}(K,orders,terms) + end +end + +@inline Base.size(a::CartProdPolyBasis{D,V}) where {D,V} = (length(a.terms)*num_indep_components(V),) +@inline get_order(b::CartProdPolyBasis) = b.max_order + +function testvalue(::Type{CartProdPolyBasis{D,V,PT}}) where {D,V,PT} + CartProdPolyBasis{D}(PT,V,tfill(0,Val(D)),CartesianIndex{D}[]) +end + +function CartProdPolyBasis( + ::Type{PT}, + ::Val{D}, + ::Type{V}, + orders::NTuple{D,Int}, + terms::Vector{CartesianIndex{D}}) where {PT<:Polynomial,D,V} + + CartProdPolyBasis{D}(PT,V,orders,terms) +end + +""" + CartProdPolyBasis(::Type{PT}, ::Val{D}, ::Type{V}, orders::Tuple [, filter=_q_filter]) + +This constructor allows to pass a tuple `orders` containing the maximal +polynomial order to be used in each of the `D` spatial dimensions in order to +construct a tensorial anisotropic `D`-multivariate space 𝕊. + +If a filter is provided, it is applied on the cartesian product terms +CartesianIndices(`orders`), with maximum(`orders`) as order argument. +""" +function CartProdPolyBasis( + ::Type{PT}, ::Val{D}, ::Type{V}, orders::NTuple{D,Int}, filter::Function=_q_filter + ) where {PT,D,V} + + terms = _define_terms(filter, orders) + CartProdPolyBasis{D}(PT,V,orders,terms) +end + +""" + CartProdPolyBasis(::Type{PT}, ::Type{V}, ::Val{D}, order::Int [, filter=_q_filter]) + +Return a `CartProdPolyBasis{D,V,order,PT}` where 𝕊 is defined by the terms +filtered by + + term -> filter(term, order). + +See the [Filter functions](@ref) section of the documentation for more details. +""" +function CartProdPolyBasis( + ::Type{PT}, VD::Val{D}, ::Type{V}, order::Int, filter::Function=_q_filter) where {PT,D,V} + + orders = tfill(order,VD) + CartProdPolyBasis(PT,Val(D),V,orders,filter) +end + +# API + +""" + get_exponents(b::CartProdPolyBasis) + +Get a vector of tuples with the exponents of all the terms in the basis of 𝕊, +the components scalar space of `b`. + +# Example + +```jldoctest +using Gridap.Polynomials + +b = MonomialBasis(Val(2),Float64,2) + +exponents = get_exponents(b) + +println(exponents) + +# output +Tuple{Int,Int}[(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2)] +``` +""" +function get_exponents(b::CartProdPolyBasis) + indexbase = 1 + [ Tuple(t) .- indexbase for t in b.terms ] +end + +function get_orders(b::CartProdPolyBasis) + b.orders +end + +################################# +# nD evaluations implementation # +################################# + +function _evaluate_nd!( + b::CartProdPolyBasis{D,V,PT}, x, + r::AbstractMatrix, i, + c::AbstractMatrix{T}, ::Val) where {D,V,PT,T} + + for d in 1:D + Kd = b.orders[d] + _evaluate_1d!(PT,Kd,c,x,d) + end + + k = 1 + for ci in b.terms + + s = one(T) + for d in 1:D + @inbounds s *= c[d,ci[d]] + end + + k = _cartprod_set_value!(r,i,s,k) + end +end + +""" + _cartprod_set_value!(r::AbstractMatrix{<:Real},i,s,k) + + r[i,k] = s; return k+1 + +`s` is scalar +""" +function _cartprod_set_value!(r::AbstractMatrix{<:Real},i,s,k) + @inbounds r[i,k] = s + k+1 +end + +""" + _cartprod_set_value!(r::AbstractMatrix{V},i,s::T,k,l) + +``` +r[i,k] = V(s, 0, ..., 0) +r[i,k+1] = V(0, s, 0, ..., 0) +⋮ +r[i,k+N-1] = V(0, ..., 0, s) +return k+N +``` + +where `N = num_indep_components(V)`, and `s` is scalar. +""" +function _cartprod_set_value!(r::AbstractMatrix{V},i,s::T,k) where {V,T} + ncomp = num_indep_components(V) + z = zero(T) + @inbounds for j in 1:ncomp + r[i,k] = ntuple(i -> ifelse(i == j, s, z),Val(ncomp)) + k += 1 + end + k +end + +function _gradient_nd!( + b::CartProdPolyBasis{D,V,PT}, x, + r::AbstractMatrix{G}, i, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + s::MVector{D,T}, ::Val{K}) where {D,V,PT,G,T,K} + + for d in 1:D + _derivatives_1d!(PT,K,(c,g),x,d) + end + + k = 1 + @inbounds for ci in b.terms + + s[:] .= one(T) + + for q in 1:D + for d in 1:D + if d != q + s[q] *= c[d,ci[d]] + else + s[q] *= g[d,ci[d]] + end + end + end + + k = _cartprod_set_derivative!(r,i,s,k,V) + end +end + +""" + _cartprod_set_derivative!(r::AbstractMatrix{G},i,s,k,::Type{<:Real}) + +``` +r[i,k] = s = (∇bᵏ)(xi); return k+1 +``` + +where bᵏ is the kᵗʰ basis polynomial. Note that `r[i,k]` is a `VectorValue` or +`TensorValue` and `s` a `MVector` or `MMatrix` respectively, of same size. +""" +function _cartprod_set_derivative!( + r::AbstractMatrix{G},i,s,k,::Type{<:Real}) where G + + @inbounds r[i,k] = s + k+1 +end + +""" + _cartprod_set_derivative!(r::AbstractMatrix{G},i,s,k,::Type{V}) + +``` +z = zero(s) +r[i,k] = G(s…, z…, ..., z…) = (Dbᵏ )(xi) +r[i,k+1] = G(z…, s…, z…, ..., z…) = (Dbᵏ⁺¹ )(xi) +⋮ +r[i,k+n-1] = G(z…, ..., z…, s…) = (Dbᵏ⁺ⁿ⁻¹)(xi) +return k+n +``` + +Note that `r[i,k]` is a `TensorValue` or `ThirdOrderTensorValue` and `s` a +`MVector` or `MMatrix`. +""" +@generated function _cartprod_set_derivative!( + r::AbstractMatrix{G},i,s,k,::Type{V}) where {G,V} + # Git blame me for readable non-generated version + + w = zero(V) + m = Array{String}(undef, size(G)) + N_val_dims = length(size(V)) + s_size = size(G)[1:end-N_val_dims] + + body = "T = eltype(s); z = zero(T);" + for ci in CartesianIndices(s_size) + id = join(Tuple(ci)) + body *= "@inbounds s$id = s[$ci];" + end + + for j in CartesianIndices(w) + for i in CartesianIndices(m) + m[i] = "z" + end + for ci in CartesianIndices(s_size) + id = join(Tuple(ci)) + m[ci,j] = "s$id" + end + body *= "@inbounds r[i,k] = ($(join(tuple(m...), ", ")));" + body *= "k = k + 1;" + end + + body = Meta.parse(string("begin ",body," end")) + return Expr(:block, body ,:(return k)) +end + +# Specialization for SymTensorValue and SymTracelessTensorValue, +# necessary as long as outer(Point, V<:AbstractSymTensorValue)::G does not +# return a tensor type G that implements the appropriate symmetries of the +# gradient (and hessian) +@generated function _cartprod_set_derivative!( + r::AbstractMatrix{G},i,s,k,::Type{V}) where {G,V<:AbstractSymTensorValue{D}} where D + # Git blame me for readable non-generated version + + T = eltype(s) + m = Array{String}(undef, size(G)) + s_length = size(G)[1] + + is_traceless = V <: SymTracelessTensorValue + skip_last_diagval = is_traceless ? 1 : 0 # Skid V_DD if traceless + + body = "z = $(zero(T));" + for i in 1:s_length + body *= "@inbounds s$i = s[$i];" + end + + for c in 1:(D-skip_last_diagval) # Go over cols + for r in c:D # Go over lower triangle, current col + for i in eachindex(m) + m[i] = "z" + end + for i in 1:s_length # indices of the Vector s + m[i,r,c] = "s$i" + if (r!=c) + m[i,c,r] = "s$i" + elseif is_traceless # V_rr contributes negatively to V_DD (tracelessness) + m[i,D,D] = "-s$i" + end + end + body *= "@inbounds r[i,k] = ($(join(tuple(m...), ", ")));" + body *= "k = k + 1;" + end + end + + body = Meta.parse(string("begin ",body," end")) + return Expr(:block, body ,:(return k)) +end + +function _hessian_nd!( + b::CartProdPolyBasis{D,V,PT}, x, + r::AbstractMatrix{G}, i, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + h::AbstractMatrix{T}, + s::MMatrix{D,D,T}, ::Val{K}) where {D,V,PT,G,T,K} + + for d in 1:D + _derivatives_1d!(PT,K,(c,g,h),x,d) + end + + k = 1 + + @inbounds for ci in b.terms + + s[:] = one(T) + + for t in 1:D + for q in 1:D + for d in 1:D + if d != q && d != t + s[t,q] *= c[d,ci[d]] + elseif d == q && d ==t + s[t,q] *= h[d,ci[d]] + else + s[t,q] *= g[d,ci[d]] + end + end + end + end + + k = _cartprod_set_derivative!(r,i,s,k,V) + end +end + diff --git a/src/Polynomials/ChangeBasis.jl b/src/Polynomials/ChangeBasis.jl index d10427338..92a75ad0a 100644 --- a/src/Polynomials/ChangeBasis.jl +++ b/src/Polynomials/ChangeBasis.jl @@ -13,7 +13,7 @@ using Gridap.Polynomials D = 2 order = 1 -f = MonomialBasis{D}(Float64,order) +f = MonomialBasis(Val(D),Float64,order) nodes = Point{2,Int}[(0,0),(1,0),(0,1),(1,1)] change = inv(evaluate(f,nodes)) @@ -67,6 +67,7 @@ function evaluate!(cache,b::BasisFromChangeOfBasis,x) c.array end +# Aren't next 4 functions out of date and removable ? function return_gradient_cache(b::BasisFromChangeOfBasis,x) cb = return_gradient_cache(b.basis,x) bx = evaluate_gradient!(cb,b.basis,x) diff --git a/src/Polynomials/ChebyshevBases.jl b/src/Polynomials/ChebyshevBases.jl new file mode 100644 index 000000000..40fd880ce --- /dev/null +++ b/src/Polynomials/ChebyshevBases.jl @@ -0,0 +1,87 @@ +""" + Chebyshev{kind} <: Polynomial + +Type representing Chebyshev polynomials of the +- first kind: `Chebyshev{:T}` +- second kind: `Chebyshev{:U}` +C.f. [Chebyshev polynomials](@ref) section. +""" +struct Chebyshev{kind} <: Polynomial + Chebyshev{:T}() = new{:T}() + Chebyshev{:U}() = new{:U}() +end + +isHierarchical(::Type{<:Chebyshev}) = true + +""" + ChebyshevBasis{D,V,kind} = CartProdPolyBasis{D,V,Chebyshev{kind}} + +Alias for cartesian product Chebyshev basis, scalar valued or multivalued. +""" +const ChebyshevBasis{D,V,kind} = CartProdPolyBasis{D,V,Chebyshev{kind}} + +""" + ChebyshevBasis(::Val{D}, ::Type{V}, order::Int, terms::Vector; kind=:T) + ChebyshevBasis(::Val{D}, ::Type{V}, order::Int [, filter::Function; kind=:T]) + ChebyshevBasis(::Val{D}, ::Type{V}, orders::Tuple [, filter::Function; kind=:T]) + +High level constructors of [`ChebyshevBasis`](@ref). +""" +ChebyshevBasis(args...; kind=:T) = CartProdPolyBasis(Chebyshev{kind}, args...) + +function CartProdPolyBasis( + ::Type{Chebyshev{:U}}, ::Val{D}, ::Type{V}, ::Int) where {D, V} + + @notimplemented "1D evaluation for second kind need to be implemented here" +end + + +# 1D evaluation implementation + +function _evaluate_1d!( + ::Type{Chebyshev{kind}},K,c::AbstractMatrix{T},x,d) where {kind,T<:Number} + + if iszero(K) + @inbounds c[d,1] = one(T) + return + end + + n = K + 1 # n > 1 + ξ = (2*x[d] - 1) # ξ ∈ [-1,1] + ξ2 = 2*ξ + + @inbounds c[d,1] = one(T) + @inbounds c[d,2] = (kind == :T) ? ξ : ξ2 + for i in 3:n + @inbounds c[d,i] = c[d,i-1]*ξ2 - c[d,i-2] + end +end + +function _gradient_1d!( + ::Type{Chebyshev{:T}},K,g::AbstractMatrix{T},x,d) where T<:Number + + if iszero(K) + @inbounds g[d,1] = zero(T) + return + end + + n = K + 1 # n>1 + z = zero(T) + o = one(T) + ξ = T(2*x[d] - 1) + dξdx = T(2.0) + + unm1 = o + un = 2*ξ + @inbounds g[d,1] = z # dT_0 = 0 + @inbounds g[d,2] = dξdx*o # dT_1 = 1*U_0 = 1 + for i in 3:n + @inbounds g[d,i] = dξdx*(i-1)*un # dT_i = i*U_{i-1} + un, unm1 = 2*ξ*un - unm1, un + end +end + +_evaluate_1d!(::Type{Chebyshev{:U}},K,h::AbstractMatrix{T},x,d) where T<:Number = @notimplemented +_gradient_1d!(::Type{Chebyshev{:U}},K,h::AbstractMatrix{T},x,d) where T<:Number = @notimplemented +_hessian_1d!( ::Type{<:Chebyshev}, K,h::AbstractMatrix{T},x,d) where T<:Number = @notimplemented + diff --git a/src/Polynomials/CompWiseTensorPolyBases.jl b/src/Polynomials/CompWiseTensorPolyBases.jl new file mode 100644 index 000000000..f08bba803 --- /dev/null +++ b/src/Polynomials/CompWiseTensorPolyBases.jl @@ -0,0 +1,295 @@ +""" + CompWiseTensorPolyBasis{D,V,PT} <: PolynomialBasis{D,V,PT} + +"Polynomial basis of component wise tensor product polynomial spaces" + +Polynomial basis for a `D`-multivariate `V`-valued polynomial space: + +`V`(𝕊₁, ∅, ..., ∅) ⊕ `V`(∅, 𝕊₂, ∅, ..., ∅) ⊕ ... ⊕ `V`(∅, ..., ∅, 𝕊ₗ) + +with l>1, where the scalar `D`-multivariate spaces 𝕊ⱼ (for 1 ≤ j ≤ l) of each +(independent) component of `V` is defined by a list of terms like the component +space of [`CartProdPolyBasis`](@ref). However, `CompWiseTensorPolyBasis` uses l +independent terms lists for each component of `V`. + +Any 1D polynomial family `PT<:Polynomial` and any tensor-value type `V<:MultiValue` is usable. + +The 1D basis used for direction/coordinate `n` of component `j` is `orders[j,n]` +where the `orders` matrix is given in the constructors. + +First and second order derivatives are supported, as long as the resulting +tensor is of order maximum 3. + +!!! warning + If `PT` is not hierarchical, the 1D bases in a direction `n` are different + for different components if `orders[:,n]` are not all the same. + +# Examples +These return instances of `CompWiseTensorPolyBasis` +```jldoctest +# a basis for Raviart-Thomas on quadrilateral with divergence in ℚ₃ +b = FEEC_poly_basis(Val(2),Float64,4,1,:Q⁻; rotate_90) + +# a basis for Raviart-Thomas on hexahedra with divergence in ℚ₃ +b = FEEC_poly_basis(Val(3),Float64,4,2,:Q⁻) + +# a basis for Nedelec on triangle with curl in ℚ₃ +b = FEEC_poly_basis(Val(2),Float64,4,1,:Q⁻) + +# a basis for Nedelec on hexahedra with curl in ℚ₃ +b = FEEC_poly_basis(Val(3),Float64,4,1,:Q⁻) +``` +""" +struct CompWiseTensorPolyBasis{D,V,PT} <: PolynomialBasis{D,V,PT} + num_poly::Int + max_order::Int + orders::Matrix{Int} + comp_terms::Vector{Vector{CartesianIndex{D}}} # of length L + + function CompWiseTensorPolyBasis{D}( + ::Type{PT}, ::Type{V}, orders::Matrix{Int}, comp_terms::Vector{Vector{CartesianIndex{D}}}) where {D,PT<:Polynomial,V} + + @check D > 0 + L = size(orders,1) + msg1 = "The orders matrix rows number must match the number of independent components of V" + @check L == num_indep_components(V) == length(comp_terms) msg1 + msg2 = "The Component Wise construction is useless for one component, use CartProdPolyBasis instead" + @check L > 1 msg2 + msg3 = "The orders matrix column number must match the number of spatial dimensions" + @check size(orders,2) == D msg3 + @check isconcretetype(PT) "PT needs to be a concrete <:Polynomial type" + + K = maximum(orders) + num_poly = mapreduce(length, +, comp_terms) + + #TODO check orders in `orders` greater or equal than max index in terms + + new{D,V,PT}(num_poly,K,orders,comp_terms) + end +end + +""" + CompWiseTensorPolyBasis{D}(PT, V, orders::Matrix{Int}) + +Define each component scalar space 𝕊ⱼ as a the full tensor product space of 1D +spaces of orders α(j,n) for 1 ≤ n ≤ `D`, that is: + +𝕊₁ = ℙα₁₁(x₁) ⊗ … ⊗ ℙα₁`D`(x`D`)\\ +⋮\\ +𝕊ⱼ = ⊗ₙ ℙαⱼₙ(xₙ)\\ +⋮\\ +𝕊ₗ = ℙαₗ₁(x₁) ⊗ … ⊗ ℙαₗ`D`(x`D`) + +The l×`D` matrix α=`orders` is given in the constructor. +`PT` is a [`Polynomial `](@ref) type, `V` a number type, L is the number of +independent components of `V`. +""" +function CompWiseTensorPolyBasis{D}(::Type{PT}, ::Type{V}, orders::Matrix{Int}) where {D,PT,V} + + L = size(orders,1) + msg1 = "The orders matrix rows number must match the number of independent components of V" + @check L == num_indep_components(V) msg1 + msg3 = "The orders matrix column number must match the number of spatial dimensions" + @check size(orders,2) == D msg3 + + comp_terms = _compute_comp_terms_(Val(D),V,orders) + + CompWiseTensorPolyBasis{D}(PT,V,orders,comp_terms) +end + +Base.size(b::CompWiseTensorPolyBasis) = ( b.num_poly, ) +get_order(b::CompWiseTensorPolyBasis) = b.max_order +get_orders(b::CompWiseTensorPolyBasis) = Tuple(maximum(b.orders; dims=1)) + +function testvalue(::Type{<:CompWiseTensorPolyBasis{D,V,PT}}) where {D,V,PT} + L = num_indep_components(V) + orders = zero(Matrix{Int}(undef, (L,D))) + CompWiseTensorPolyBasis{D}(PT,V,orders) +end + +""" + get_comp_terms(f::CompWiseTensorPolyBasis{D,V}) + +Return a tuple (terms\\_1, ..., terms\\_l, ..., terms\\_L) containing, for each +component of V, the Cartesian indices iterator over the terms that define 𝕊ˡ, +that is all elements of {1 : `o`(l,1)+1} × {1 : `o`(l,2)+1} × … × {1 : `o`(l,D)+1}. + +E.g., if `orders=[ 0 1; 1 0]`, then the `comp_terms` are +`( CartesianIndices{2}((1,2)), CartesianIndices{2}((2,1)) )`. +""" +get_comp_terms(f::CompWiseTensorPolyBasis) = f.comp_terms + +function _compute_comp_terms_(::Val{D}, ::Type{V}, orders) where {D,V} + L = num_indep_components(V) + _terms(l) = CartesianIndex{D}[ci for ci in CartesianIndices(Tuple(orders[l,:] .+ 1)) if true] + Vector{CartesianIndex{D}}[ _terms(l) for l in 1:L ] +end + + +################################# +# nD evaluations implementation # +################################# + +function _evaluate_nd!( + b::CompWiseTensorPolyBasis{D,V,PT}, x, + r::AbstractMatrix, i, + c::AbstractMatrix{T}, ::Val{K}) where {D,V,PT,T,K} + + # optimization if PT is hierarchical: lower order polynomials do not depend on the maximum order + if isHierarchical(PT) + for d in 1:D + _evaluate_1d!(PT,K,c,x,d) + end + end + + k = 1 + @inbounds for (l,terms) in enumerate(get_comp_terms(b)) + + if !isHierarchical(PT) + for d in 1:D + # compute 1D polynomials for first component or recompute them if order changed + kld = b.orders[l,d] + if isone(l) || kld ≠ b.orders[l-1,d] + _evaluate_1d!(PT,kld,c,x,d) + end + end + end + + for ci in terms + s = one(T) + + for d in 1:D + s *= c[d,ci[d]] + end + + k = _comp_wize_set_value!(r,i,s,k,l) + end + end +end + +""" + _comp_wize_set_value!(r::AbstractMatrix{V},i,s::T,k,l) + +``` +r[i,k] = V(0, ..., 0, s, 0, ..., 0); return k+1 +``` + +where `s` is at position `l` in `V<:MultiValue`. +""" +function _comp_wize_set_value!(r::AbstractMatrix{V},i,s::T,k,l) where {V,T} + z = zero(T) + ncomp = num_indep_components(V) + r[i,k] = ntuple(i -> ifelse(i == l, s, z),Val(ncomp)) + return k + 1 +end + +function _gradient_nd!( + b::CompWiseTensorPolyBasis{D,V,PT}, x, + r::AbstractMatrix{G}, i, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + s::MVector{D,T}, ::Val{K}) where {D,V,PT,G,T,K} + + if isHierarchical(PT) + for d in 1:D + _derivatives_1d!(PT,K,(c,g),x,d) + end + end + + k = 1 + @inbounds for (l,terms) in enumerate(get_comp_terms(b)) + + if !isHierarchical(PT) + for d in 1:D + kld = b.orders[l,d] + if isone(l) || kld ≠ b.orders[l-1,d] + _derivatives_1d!(PT,kld,(c,g),x,d) + end + end + end + + for ci in terms + s .= one(T) + + for q in 1:D + for d in 1:D + if d != q + s[q] *= c[d,ci[d]] + else + s[q] *= g[d,ci[d]] + end + end + end + + k = _comp_wize_set_derivative!(r,i,s,k,l,V) + end + end +end + +""" + _comp_wize_set_derivative!(r::AbstractMatrix{G},i,s,k,l,::Type{V}) + +``` +z = zero(s) +r[i,k] = G(z…, ..., z…, s…, z…, ..., z…) = (Dbᵏ)(xi) +return k+1 +``` + +where `s…` is the `l`ᵗʰ set of components. This is the gradient or hessian of +the `k`ᵗʰ basis polynomial, whose nonzero component in `V` is the `l`ᵗʰ. +""" +function _comp_wize_set_derivative!( + r::AbstractMatrix{G},i,s,k,l,::Type{V}) where {G,V} + @inbounds r[i,k] = MultiValue(s) ⊗ V(ntuple( i -> Int(i==l), Val(num_indep_components(V)))) + k+1 +end + +function _hessian_nd!( + b::CompWiseTensorPolyBasis{D,V,PT}, x, + r::AbstractMatrix{H}, i, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + h::AbstractMatrix{T}, + s::MMatrix{D,D,T}, ::Val{K}) where {D,V,PT,H,T,K} + + if isHierarchical(PT) + for d in 1:D + _derivatives_1d!(PT,K,(c,g,h),x,d) + end + end + + + k = 1 + @inbounds for (l,terms) in enumerate(get_comp_terms(b)) + + if !isHierarchical(PT) + for d in 1:D + kld = b.orders[l,d] + if isone(l) || kld ≠ b.orders[l-1,d] + _derivatives_1d!(PT,kld,(c,g,h),x,d) + end + end + end + + for ci in terms + s .= one(T) + + for r in 1:D + for q in 1:D + for d in 1:D + if d != q && d != r + s[r,q] *= c[d,ci[d]] + elseif d == q && d ==r + s[r,q] *= h[d,ci[d]] + else + s[r,q] *= g[d,ci[d]] + end + end + end + end + + k = _comp_wize_set_derivative!(r,i,s,k,l,V) + end + end +end + diff --git a/src/Polynomials/Deprecated.jl b/src/Polynomials/Deprecated.jl new file mode 100644 index 000000000..f8a494432 --- /dev/null +++ b/src/Polynomials/Deprecated.jl @@ -0,0 +1,59 @@ +""" + num_terms(a::PolynomialBasis) + +!!! warning + Deprecated in favor of length(a). +""" +function num_terms end +@deprecate num_terms(a::PolynomialBasis) length(a) + +@deprecate MonomialBasis{D}(args...) where D MonomialBasis(Val(D), args...) + +""" + PCurlGradMonomialBasis{D}(T,order::Int) where D + +!!! warning + Deprecated in favor of `FEEC_poly_basis(Val(D),T,order+1,D-1,:P⁻,Monomial; rotate_90=(D==2))`. +""" +struct PCurlGradMonomialBasis{D} + function PCurlGradMonomialBasis end # prevents any instantiation +end +@deprecate PCurlGradMonomialBasis{D}(::Type{T},order::Int) where {T,D} FEEC_poly_basis(Val(D),T,order+1,D-1,:P⁻,Monomial; rotate_90=(D==2)) + +""" + QGradMonomialBasis{D}(T,order::Int) where D + +!!! warning + Deprecated in favor of `FEEC_poly_basis(Val(D),T,order+1,1,:Q⁻,Monomial)`. +""" +struct QGradMonomialBasis{D} + function QGradMonomialBasis end +end +@deprecate QGradMonomialBasis{D}(::Type{T},order::Int) where {T,D} FEEC_poly_basis(Val(D),T,order+1,1,:Q⁻,Monomial) + +""" + QCurlGradMonomialBasis{D}(T,order::Int) where D + +!!! warning + Deprecated in favor of `FEEC_poly_basis(Val(D),T,order+1,D-1,:Q⁻,Monomial; rotate_90=(D==2))`. +""" +struct QCurlGradMonomialBasis{D} + function QCurlGradMonomialBasis end +end +@deprecate QCurlGradMonomialBasis{D}(::Type{T},order::Int) where {T,D} FEEC_poly_basis(Val(D),T,order+1,D-1,:Q⁻,Monomial; rotate_90=(D==2)) + +struct NedelecPrebasisOnSimplex{D} + function NedelecPrebasisOnSimplex end +end +@deprecate NedelecPrebasisOnSimplex{D}(order::Int) where D NedelecPolyBasisOnSimplex{D}(Monomial, Float64, order) false + +""" + JacobiPolynomialBasis{D}(args...) where D + +!!! warning + Deprecated in favor of LegendreBasis(Val(D), args...). +""" +struct JacobiPolynomialBasis{D} + function JacobiPolynomialBasis end +end +@deprecate JacobiPolynomialBasis{D}(args...) where D LegendreBasis(Val(D), args...) diff --git a/src/Polynomials/ExteriorCalculusBases.jl b/src/Polynomials/ExteriorCalculusBases.jl new file mode 100644 index 000000000..d9d2be8dc --- /dev/null +++ b/src/Polynomials/ExteriorCalculusBases.jl @@ -0,0 +1,153 @@ +############################################# +# (Proxied) Form valued nD polynomial bases # +############################################# + +_ensure_hierarchical(PT) = !isHierarchical(PT) && @unreachable "Polynomial family must be hierarchical, got $PT." + +function _default_poly_type(F) + F ∈ (:P, :P⁻) && return Bernstein + F ∈ (:Q⁻,:S) && return ModalC0 + Monomial +end + +""" + FEEC_poly_basis(::Val{D},T,r,k,F::Symbol, PT=_default_poly_type(F); kwargs...) + +"Factory for polynomial basis of Finite Element Exterior Calculus spaces" + +Return, if it is implemented, a polynomial basis for the space `FᵣΛᵏ` in +dimension `D`, with `T` the scalar component type and `PT<:Polynomial` the +polynomial basis family. + +The default `PT` is `Bernstein` on simplices and `ModalC0` on D-cubes. + +# Arguments +- `D`: spatial dimension +- `T::Type`: scalar components type +- `r::Int`: polynomial order +- `k::Int`: form order +- `F::Symbol`: family, i.e. `:P⁻`, `:P`, `:Q⁻` or `:S` +### kwargs +- `rotate_90::Bool`: only if `D`=2 and `k`=1, tells to use the vector proxy corresponding to div conform function instead of curl conform ones. +- `vertices=nothing`: for `PT=Bernstein` bases on simplices (`F = :P` or `:P⁻`), the basis is defined on the simplex defined by `vertices` instead of the reference one. +- `cart_prod=false`: for `k`=0 or `k=D`, authorise `T` to be a tensor type for Cartesian product of the scalar polynomial space. +""" # document DG_calc once it's implemented +function FEEC_poly_basis(::Val{D},::Type{T},r,k,F::Symbol,PT=_default_poly_type(F); + DG_calc=false, rotate_90=false, vertices=nothing, cart_prod=false) where {D,T} + + @assert PT <: Polynomial + + # these call FEEC_space_definition_checks internally + if !cart_prod + F == :P⁻ && PT == Bernstein && return BarycentricPmΛBasis(Val(D),T,r,k,vertices; rotate_90, DG_calc) + F == :P && PT == Bernstein && return BarycentricPΛBasis( Val(D),T,r,k,vertices; rotate_90, DG_calc) + end + + + FEEC_space_definition_checks(Val(D), T, r, k, F, rotate_90, DG_calc; cart_prod) + @notimplementedif DG_calc # This ensures 0≤k≤D≤3 + + if k == 0 + # Scalar H1 conforming functions + @notimplementedif r < 0 + if F == :P⁻ || F == :P # Lagrange, ℙr space + PT == Bernstein && return BernsteinBasisOnSimplex{D}(T,r,vertices) # only if cart_prod + _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),T,r,_p_filter) + elseif F == :Q⁻ # Lagrange, ℚr space + CartProdPolyBasis(PT,Val(D),T,r,_q_filter) + elseif F == :S # Lagrange, 𝕊r space + PT==ModalC0 || _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),T,r,_ser_filter) + end + + + elseif k == D + # Scalar L2 conforming densities + if F == :P⁻ # Lagrange, ℙr₋1 space + PT == Bernstein && return BernsteinBasisOnSimplex{D}(T,r-1,vertices) # only if cart_prod + _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),T,r-1,_p_filter) + elseif F == :P # Lagrange, ℙr space + PT == Bernstein && return BernsteinBasisOnSimplex{D}(T,r,vertices) # only if cart_prod + _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),T,r,_p_filter) + elseif F == :Q⁻ # Lagrange, ℚr₋1 space + CartProdPolyBasis(PT,Val(D),T,r-1,_q_filter) + elseif F == :S # Serendipity Lagrange ≡ ℙr space + PT == Bernstein && return BernsteinBasisOnSimplex{D}(T,r,vertices) # only if cart_prod + _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),T,r,_p_filter) + end + + + elseif k == 1 # and D > 1 + V = VectorValue{D,T} + if D == 2 + if F == :P⁻ + if rotate_90 # Raviart-Thomas + # former PCurlGradBasis(PT,Val(D),T,r-1) + RaviartThomasPolyBasis{D}(PT, T, r-1) + else # Nedelec + # former PGradBasis(PT,Val(D),T,r-1) + NedelecPolyBasisOnSimplex{D}(PT,T,r-1) + end + elseif F == :P # BDM on simplex, cannot be Bernstein + _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),V,r,Polynomials._p_filter) # rotation not needed + elseif F == :Q⁻ + if rotate_90 # Raviart-Thomas + # former QCurlGradBasis(PT,Val(D),T,r-1) + orders = [ r-1 + (i==j ? 1 : 0) for i in 1:D, j in 1:D ] + CompWiseTensorPolyBasis{D}(PT, V, orders) + else # Nedelec + # former QGradBasis(PT,Val(D),T,r-1) + orders = [ r-1 + (i==j ? 0 : 1) for i in 1:D, j in 1:D ] + CompWiseTensorPolyBasis{D}(PT, V, orders) + end + elseif F == :S # BDM on D-cubes + @notimplemented + end + + elseif D == 3 + if F == :P⁻ # First kind Nedelec, cannot be Bernstein + # former PGradBasis(PT,Val(D),T,r-1) + NedelecPolyBasisOnSimplex{D}(PT,T,r-1) + elseif F == :P # Second kind Nedelec, cannot be Bernstein + _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),V,r,Polynomials._p_filter) # rotation not needed + elseif F == :Q⁻ # First kind Nedelec + # former QGradBasis(PT,Val(D),T,r-1) + orders = [ r-1 + (i==j ? 0 : 1) for i in 1:D, j in 1:D ] + CompWiseTensorPolyBasis{D}(PT, V, orders) + elseif F == :S # "Serendipity second kind Nedelec" ? + @notimplemented + end + + else # D > 3 + @notimplemented + end + + + elseif k == 2 # D > 2 + @notimplementedif D > 3 + V = VectorValue{D,T} + # D == 3 + if F == :P⁻ # Raviart-Thomas, cannot be Bernstein + # former PCurlGradBasis(PT,Val(D),T,r-1) + RaviartThomasPolyBasis{D}(PT, T, r-1) + elseif F == :P # "3D BDM" ? cannot be Bernstein + _ensure_hierarchical(PT) + CartProdPolyBasis(PT,Val(D),V,r,Polynomials._p_filter) # rotation not needed + elseif F == :Q⁻ # Raviart-Thomas + # former QCurlGradBasis(PT,Val(D),T,r-1) + orders = [ r-1 + (i==j ? 1 : 0) for i in 1:D, j in 1:D ] + CompWiseTensorPolyBasis{D}(PT, V, orders) + elseif F == :S # "3D Serendipity BDM" ? + @notimplemented + end + + else + @unreachable + end +end diff --git a/src/Polynomials/JacobiPolynomialBases.jl b/src/Polynomials/JacobiPolynomialBases.jl deleted file mode 100644 index 896fc9c2d..000000000 --- a/src/Polynomials/JacobiPolynomialBases.jl +++ /dev/null @@ -1,359 +0,0 @@ -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_indep_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} - @check D == length(eltype(x)) "Incorrect number of point components" - np = length(x) - ndof = length(f) - 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) - 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) - 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) - 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) - 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) - 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) - 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/LegendreBases.jl b/src/Polynomials/LegendreBases.jl new file mode 100644 index 000000000..898689f4f --- /dev/null +++ b/src/Polynomials/LegendreBases.jl @@ -0,0 +1,69 @@ +""" + Legendre <: Polynomial + +Type representing the normalised shifted Legendre polynomials, c.f. [Legendre polynomials](@ref) section. +""" +struct Legendre <: Polynomial end + +isHierarchical(::Type{Legendre}) = true + +""" + LegendreBasis{D,V} = CartProdPolyBasis{D,V,Legendre} + +Alias for cartesian product Legendre basis, scalar valued or multi-valued. +""" +const LegendreBasis{D,V} = CartProdPolyBasis{D,V,Legendre} + +""" + LegendreBasis(::Val{D}, ::Type{V}, order::Int, terms::Vector) + LegendreBasis(::Val{D}, ::Type{V}, order::Int [, filter::Function]) + LegendreBasis(::Val{D}, ::Type{V}, orders::Tuple [, filter::Function]) + +High level constructors of [`LegendreBasis`](@ref). +""" +LegendreBasis(args...) = CartProdPolyBasis(Legendre, args...) + + +# 1D evaluation implementation + +# TODO optimize evaluation by using the iterative formula explicitely + +function _evaluate_1d!(::Type{Legendre},K::Int,c::AbstractMatrix{T},x,d) where T<:Number + n = K + 1 + @inbounds c[d,1] = one(T) + if n > 1 + ξ = ( 2*x[d] - 1 ) + for i in 2:n + # The sqrt(2i-1) factor normalizes the basis polynomial for L2 scalar + # product on ξ∈[0,1], indeed: + # ∫[0,1] Pn(2ξ-1)^2 dξ = 1/2 ∫[-1,1] Pn(t)^2 dt = 1/(2n+1) + # C.f. Eq. (1.25) in Section 1.1.5 in Ern & Guermond book (2013). + @inbounds c[d,i] = sqrt(2*i-1)*jacobi(ξ,i-1,0,0) + end + end +end + +function _gradient_1d!(::Type{Legendre},K::Int,g::AbstractMatrix{T},x,d) where T<:Number + n = K + 1 + z = zero(T) + @inbounds g[d,1] = z + if n > 1 + ξ = ( 2*x[d] - 1 ) + for i in 2:n + @inbounds g[d,i] = sqrt(2*i-1)*i*jacobi(ξ,i-2,1,1) + end + end +end + +function _hessian_1d!(::Type{Legendre},K::Int,h::AbstractMatrix{T},x,d) where T<:Number + n = K + 1 + z = zero(T) + @inbounds h[d,1] = z + if n > 1 + @inbounds h[d,2] = z + ξ = ( 2*x[d] - 1 ) + for i in 3:n + @inbounds h[d,i] = sqrt(2*i-1)*(i*(i+1)/2)*jacobi(ξ,i-3,2,2) + end + end +end diff --git a/src/Polynomials/ModalC0Bases.jl b/src/Polynomials/ModalC0Bases.jl index 3fbe918d2..9ec3d10e0 100644 --- a/src/Polynomials/ModalC0Bases.jl +++ b/src/Polynomials/ModalC0Bases.jl @@ -1,267 +1,152 @@ -struct ModalC0BasisFunction <: Field end +""" + ModalC0 <: Polynomial + +Type representing ModalC0 polynomials, c.f. [ModalC0 polynomials](@ref) section. + +The 1D polynomials are +- ``φ₁(x) = 1-x`` +- ``φ₂(x) = x`` +- ``φᵢ(x) = Cᵢ(1-x)x𝑱ᵢ(s(x)), 3 ≤ i ≤ k`` +where ``Cᵢ`` is a constant, ``𝑱ᵢ`` the ``i``th (1,1)-Jacobi polynomial +and ``s`` an affine transformation. + +Reference: Eq. (17) in https://doi.org/10.1016/j.camwa.2022.09.027 + +The first 1D polynomial, ``1-x``, is of order ``1`` instead of ``0``, and the last one, ``x``, +is of order ``1`` istead of ``K``. So the complete 1D basis isn't hierarchical. +""" +struct ModalC0 <: Polynomial end + +isHierarchical(::Type{ModalC0}) = false + +""" + ModalC0Basis{D,V,T} <: PolynomialBasis{D,V,ModalC0} -struct ModalC0Basis{D,T,V} <: AbstractVector{ModalC0BasisFunction} +Tensor product basis of generalised modal C0 1D basis from section 5.2 in +https://doi.org/10.1016/j.camwa.2022.09.027. +See also [ModalC0 polynomials](@ref) section of the documentation. +""" +struct ModalC0Basis{D,V,T} <: PolynomialBasis{D,V,ModalC0} + max_order::Int orders::NTuple{D,Int} terms::Vector{CartesianIndex{D}} - a::Vector{Point{D,V}} - b::Vector{Point{D,V}} + a::Vector{Point{D,T}} + b::Vector{Point{D,T}} + function ModalC0Basis{D}( - ::Type{T}, + ::Type{V}, orders::NTuple{D,Int}, terms::Vector{CartesianIndex{D}}, - a::Vector{Point{D,V}}, - b::Vector{Point{D,V}}) where {D,T,V} + a::Vector{Point{D,T}}, + b::Vector{Point{D,T}}) where {D,V,T} + + _msg = "The number of bounding box points in a and b should match the number of terms" + @check length(terms) == length(a) == length(b) _msg + @check T == eltype(V) "Point and polynomial values should have the same scalar body" + K = maximum(orders, init=0) - new{D,T,V}(orders,terms,a,b) + new{D,V,T}(K,orders,terms,a,b) end end -@inline Base.size(a::ModalC0Basis{D,T,V}) where {D,T,V} = (length(a.terms)*num_indep_components(T),) -@inline Base.getindex(a::ModalC0Basis,i::Integer) = ModalC0BasisFunction() -@inline Base.IndexStyle(::ModalC0Basis) = IndexLinear() +""" + ModalC0Basis{D}(::Type{V},order::Int; [, filter][, sort!:]) + ModalC0Basis{D}(::Type{V},order::Int, a::Point ,b::Point ; [, filter][, sort!:]) + ModalC0Basis{D}(::Type{V},orders::Tuple; [, filter][, sort!:]) + ModalC0Basis{D}(::Type{V},orders::Tuple,a::Point ,b::Point ; [, filter][, sort!:]) + ModalC0Basis{D}(::Type{V},orders::Tuple,a::Vector,b::Vector; [, filter][, sort!:]) + +where `filter` is a `Function` defaulting to `_q_filter`, and `sort!` is a +`Function` defaulting to `_sort_by_nfaces!`. + +At last, all scalar basis polynomial will have its bounding box `(a[i],b[i])`, +but they are assumed iddentical if only two points `a` and `b` are provided, +and default to `a=Point{D}(0...)`, `b=Point{D}(1...)` if not provided. + +The basis is a cartesian product when multi-valued, isotropic if one `order` is +provided, or anisotropic if a `D` tuple `orders` is provided. +""" +function ModalC0Basis() end function ModalC0Basis{D}( - ::Type{T}, + ::Type{V}, orders::NTuple{D,Int}, - a::Vector{Point{D,V}}, - b::Vector{Point{D,V}}; + a::Vector{Point{D,T}}, + b::Vector{Point{D,T}}; filter::Function=_q_filter, - sort!::Function=_sort_by_nfaces!) where {D,T,V} + sort!::Function=_sort_by_nfaces!) where {D,V,T} terms = _define_terms_mc0(filter, sort!, orders) - ModalC0Basis{D}(T,orders,terms,a,b) + ModalC0Basis{D}(V,orders,terms,a,b) end function ModalC0Basis{D}( - ::Type{T}, + ::Type{V}, orders::NTuple{D,Int}, - sa::Point{D,V}, - sb::Point{D,V}; + sa::Point{D,T}, + sb::Point{D,T}; filter::Function=_q_filter, - sort!::Function=_sort_by_nfaces!) where {D,T,V} + sort!::Function=_sort_by_nfaces!) where {D,V,T} terms = _define_terms_mc0(filter, sort!, orders) a = fill(sa,length(terms)) b = fill(sb,length(terms)) - ModalC0Basis{D}(T,orders,terms,a,b) + ModalC0Basis{D}(V,orders,terms,a,b) end function ModalC0Basis{D}( - ::Type{T}, + ::Type{V}, orders::NTuple{D,Int}; filter::Function=_q_filter, - sort!::Function=_sort_by_nfaces!) where {D,T} + sort!::Function=_sort_by_nfaces!) where {D,V} - 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!) + T = eltype(V) + sa = Point{D,T}(tfill(zero(T),Val{D}())) + sb = Point{D,T}(tfill( one(T),Val{D}())) + ModalC0Basis{D}(V,orders,sa,sb; filter=filter, sort! =sort!) end function ModalC0Basis{D}( - ::Type{T}, + ::Type{V}, order::Int, - a::Vector{Point{D,V}}, - b::Vector{Point{D,V}}; + a::Vector{Point{D,T}}, + b::Vector{Point{D,T}}; filter::Function=_q_filter, - sort!::Function=_sort_by_nfaces!) where {D,T,V} + sort!::Function=_sort_by_nfaces!) where {D,V,T} orders = tfill(order,Val{D}()) - ModalC0Basis{D}(T,orders,a,b,filter=filter,sort! = sort!) + ModalC0Basis{D}(V,orders,a,b; filter=filter, sort! =sort!) end function ModalC0Basis{D}( - ::Type{T}, + ::Type{V}, order::Int; filter::Function=_q_filter, - sort!::Function=_sort_by_nfaces!) where {D,T} + sort!::Function=_sort_by_nfaces!) where {D,V} orders = tfill(order,Val{D}()) - ModalC0Basis{D}(T,orders,filter=filter,sort! = sort!) + ModalC0Basis{D}(V,orders; filter=filter, sort! =sort!) end + # API -""" - get_order(b::ModalC0Basis) -""" -function get_order(b::ModalC0Basis) - maximum(b.orders) -end +@inline Base.size(b::ModalC0Basis{D,V}) where {D,V} = (length(b.terms)*num_indep_components(V),) +@inline get_order(b::ModalC0Basis) = b.max_order -""" - 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) - 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) - 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) - 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) - 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) - 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) - 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 +function testvalue(::Type{ModalC0Basis{D,V,T}}) where {D,V,T} + orders = tfill(1,Val{D}()) + sa = Point{D,T}(tfill(zero(T),Val(D))) + sb = Point{D,T}(tfill( one(T),Val(D))) + ModalC0Basis{D}(V,orders,sa,sb) 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. @@ -296,7 +181,7 @@ end function _compute_filter_mask(terms,filter,orders) g = (0 .* orders) .+ 1 to = CartesianIndex(g) - maxorder = _maximum(orders) + maxorder = maximum(orders) term_to_is_fterm = lazy_map(t->filter(Int[Tuple(t-to)...],maxorder),terms) findall(term_to_is_fterm) end @@ -308,134 +193,83 @@ function _define_terms_mc0(filter,sort!,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 +################################# +# nD evaluations implementation # +################################# -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 +_get_static_parameters(::ModalC0Basis) = nothing -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} +function _evaluate_nd!( + basis::ModalC0Basis{D,V}, x, + r::AbstractMatrix, i, + c::AbstractMatrix{T}, ::Nothing) where {D,V,T} + + terms = basis.terms + orders = basis.orders + a = basis.a + b = basis.b - dim = D - o = one(T) k = 1 l = length(terms) - for (i,ci) in enumerate(terms) + for (n,ci) in enumerate(terms) - for d in 1:dim - _evaluate_1d_mc0!(c,x,a[i],b[i],orders[d],d) + for d in 1:D + _evaluate_1d_mc0!(c,x,a[n],b[n],orders[d],d) end - s = o - for d in 1:dim + s = one(T) + for d in 1:D @inbounds s *= c[d,ci[d]] end - k = _set_value_mc0!(v,s,k,l) - + k = _set_value_mc0!(r,i,s,k,l) end - end -@inline function _set_value_mc0!(v::AbstractVector{V},s::T,k,l) where {V,T} +@inline function _set_value_mc0!(r::AbstractMatrix{V},i,s::T,k,l) where {V,T} ncomp = num_indep_components(V) z = zero(T) for j in 1:ncomp m = k+l*(j-1) - @inbounds v[m] = ntuple(i -> ifelse(i == j, s, z),Val(ncomp)) + @inbounds r[i,m] = ntuple(p -> ifelse(p == j, s, z),Val(ncomp)) end k+1 end -@inline function _set_value_mc0!(v::AbstractVector{<:Real},s,k,l) - @inbounds v[k] = s +@inline function _set_value_mc0!(r::AbstractMatrix{<:Real},i,s,k,l) + @inbounds r[i,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}}, +function _gradient_nd!( + basis::ModalC0Basis{D,V}, x, + r::AbstractMatrix{G}, i, c::AbstractMatrix{T}, g::AbstractMatrix{T}, - ::Type{V}) where {G,T,D,V} + s::MVector{D,T}, ::Nothing) where {D,V,T,G} + + terms = basis.terms + orders = basis.orders + a = basis.a + b = basis.b - dim = D - z = zero(Mutable(VectorValue{D,T})) - o = one(T) k = 1 l = length(terms) - for (i,ci) in enumerate(terms) + for (n,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) + for d in 1:D + _evaluate_1d_mc0!(c,x,a[n],b[n],orders[d],d) + _gradient_1d_mc0!(g,x,a[n],b[n],orders[d],d) end - s = z - for i in eachindex(s) - @inbounds s[i] = o + for j in eachindex(s) + @inbounds s[j] = one(T) end - for q in 1:dim - for d in 1:dim + for q in 1:D + for d in 1:D if d != q @inbounds s[q] *= c[d,ci[d]] else @@ -444,27 +278,25 @@ function _gradient_nd_mc0!( end end - k = _set_gradient_mc0!(v,s,k,l,V) - + k = _set_derivative_mc0!(r,i,s,k,l,V) end - end -@inline function _set_gradient_mc0!( - v::AbstractVector{G},s,k,l,::Type{<:Real}) where G +@inline function _set_derivative_mc0!( + r::AbstractMatrix{G},i,s,k,l,::Type{<:Real}) where G - @inbounds v[k] = s + @inbounds r[i,k] = s k+1 end # Indexing and m definition should be fixed if G contains symmetries, that is # if the code is optimized for symmetric tensor V valued FESpaces # (if gradient_type(V) returned a symmetric higher order tensor type G) -@inline @generated function _set_gradient_mc0!( - v::AbstractVector{G},s,k,l,::Type{V}) where {V,G} +@inline @generated function _set_derivative_mc0!( + r::AbstractMatrix{G},i1,s,k,l,::Type{V}) where {V,G} # Git blame me for readable non-generated version - @notimplementedif num_indep_components(G) != num_components(G) "Not implemented for symmetric Jacobian or Hessian" - + @notimplementedif num_indep_components(V) != num_components(V) "Not implemented for symmetric Jacobian or Hessian" + m = Array{String}(undef, size(G)) N_val_dims = length(size(V)) s_size = size(G)[1:end-N_val_dims] @@ -474,7 +306,7 @@ end id = join(Tuple(ci)) body *= "@inbounds s$id = s[$ci];" end - + V_size = size(V) for (ij,j) in enumerate(CartesianIndices(V_size)) for i in CartesianIndices(m) @@ -485,46 +317,43 @@ end m[ci,j] = "s$id" end body *= "i = k + l*($ij-1);" - body *= "@inbounds v[i] = ($(join(tuple(m...), ", ")));" + body *= "@inbounds r[i1,i] = ($(join(tuple(m...), ", ")));" end body = Meta.parse(string("begin ",body," end")) return Expr(:block, body ,:(return 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}}, +function _hessian_nd!( + basis::ModalC0Basis{D,V}, x, + r::AbstractMatrix{G}, i, c::AbstractMatrix{T}, g::AbstractMatrix{T}, h::AbstractMatrix{T}, - ::Type{V}) where {G,T,D,V} + s::MMatrix{D,D,T}, ::Nothing) where {D,V,T,G} + + terms = basis.terms + orders = basis.orders + a = basis.a + b = basis.b - dim = D - z = zero(Mutable(TensorValue{D,D,T})) - o = one(T) k = 1 l = length(terms) - for (i,ci) in enumerate(terms) + for (n,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) + for d in 1:D + _evaluate_1d_mc0!(c,x,a[n],b[n],orders[d],d) + _gradient_1d_mc0!(g,x,a[n],b[n],orders[d],d) + _hessian_1d_mc0!(h,x,a[n],b[n],orders[d],d) end - s = z - for i in eachindex(s) - @inbounds s[i] = o + for j in eachindex(s) + @inbounds s[j] = one(T) end - for r in 1:dim - for q in 1:dim - for d in 1:dim + for r in 1:D + for q in 1:D + for d in 1:D if d != q && d != r @inbounds s[r,q] *= c[d,ci[d]] elseif d == q && d ==r @@ -536,8 +365,110 @@ function _hessian_nd_mc0!( end end - k = _set_gradient_mc0!(v,s,k,l,V) + k = _set_derivative_mc0!(r,i,s,k,l,V) + end +end + + +################################# +# 1D evaluations implementation # +################################# + +# Reference: equation (17) in +# +# Badia, S.; Neiva, E. & Verdugo, F.; (2022); +# Robust high-order unfitted finite elements by interpolation-based discrete extension, +# Computers & Mathematics with Applications, +# https://doi.org/10.1016/j.camwa.2022.09.027 +function _evaluate_1d_mc0!(c::AbstractMatrix{T},x,a,b,order,d) where T + @check order > 0 + n = order + 1 + o = one(T) + @inbounds c[d,1] = o - x[d] + @inbounds c[d,2] = x[d] + if n > 2 + ξ = ( 2*x[d] - ( a[d] + b[d] ) ) / ( b[d] - a[d] ) + for i in 3:n + @inbounds c[d,i] = -sqrt(2*i-3)*c[d,1]*c[d,2]*jacobi(ξ,i-3,1,1)/(i-2) + end + end +end + +function _gradient_1d_mc0!(g::AbstractMatrix{T},x,a,b,order,d) where T + @check order > 0 + n = order + 1 + o = one(T) + @inbounds g[d,1] = -o + @inbounds g[d,2] = o + if n > 2 + ξ = ( 2*x[d] - ( a[d] + b[d] ) ) / ( b[d] - a[d] ) + v1 = o - x[d] + v2 = x[d] + for i in 3:n + j, dj = jacobi_and_derivative(ξ,i-3,1,1) + @inbounds g[d,i] = -sqrt(2*i-3)*(g[d,1]*v2*j+v1*g[d,2]*j+v1*v2*(2/(b[d]-a[d]))*dj)/(i-2) + end + end +end + +function _hessian_1d_mc0!(h::AbstractMatrix{T},x,a,b,order,d) where T + @check order > 0 + n = order + 1 + z = zero(T) + o = one(T) + @inbounds h[d,1] = z + @inbounds h[d,2] = z + if n > 2 + ξ = ( 2*x[d] - ( a[d] + b[d] ) ) / ( b[d] - a[d] ) + v1 = o - x[d] + v2 = x[d] + dv1 = -o + dv2 = o + for i in 3:n + j, dj = jacobi_and_derivative(ξ,i-3,1,1) + _, d2j = jacobi_and_derivative(ξ,i-4,2,2) + @inbounds h[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 + + +####################################### +# Generic 1D internal polynomial APIs # +####################################### + +# For possible use with CartProdPolyBasis etc. +# Make it for x∈[0,1] like the other 1D bases. + +function _evaluate_1d!(::Type{ModalC0},K,c::AbstractMatrix{T},x,d) where T<:Number + if iszero(K) + @inbounds c[d,1] = one(T) + return + end + + a = zero(x) + b = zero(x) .+ one(T) + @inline _evaluate_1d_mc0!(c,x,a,b,K,d) +end + +function _gradient_1d!(::Type{ModalC0},K,g::AbstractMatrix{T},x,d) where T<:Number + if iszero(K) + @inbounds g[d,1] = zero(T) + return + end + + a = zero(x) + b = zero(x) .+ one(T) + @inline _gradient_1d_mc0!(g,x,a,b,K,d) +end +function _hessian_1d!(::Type{ModalC0},K,h::AbstractMatrix{T},x,d) where T<:Number + if iszero(K) + @inbounds h[d,1] = zero(T) + return end + a = zero(x) + b = zero(x) .+ one(T) + @inline _hessian_1d_mc0!(h,x,a,b,K,d) end diff --git a/src/Polynomials/MonomialBases.jl b/src/Polynomials/MonomialBases.jl index f75a6c592..6716a81b9 100644 --- a/src/Polynomials/MonomialBases.jl +++ b/src/Polynomials/MonomialBases.jl @@ -1,611 +1,135 @@ -struct Monomial <: Field end - -testvalue(::Type{Monomial}) = Monomial() -function testvalue(::Type{<:AbstractVector{Monomial}}) - @notimplemented -end - """ - struct MonomialBasis{D,T} <: AbstractVector{Monomial} + Monomial <: Polynomial -Type representing a basis of multivariate scalar-valued, vector-valued, or -tensor-valued, iso- or aniso-tropic monomials. The fields -of this `struct` are not public. -This type fully implements the [`Field`](@ref) interface, with up to second order -derivatives. +Type representing the monomial polynomials, c.f. [Monomials](@ref) section. """ -struct MonomialBasis{D,T} <: AbstractVector{Monomial} - orders::NTuple{D,Int} - terms::Vector{CartesianIndex{D}} - function MonomialBasis{D}( - ::Type{T}, orders::NTuple{D,Int}, terms::Vector{CartesianIndex{D}}) where {D,T} - new{D,T}(orders,terms) - end -end +struct Monomial <: Polynomial end -Base.size(a::MonomialBasis{D,T}) where {D,T} = (length(a.terms)*num_indep_components(T),) -# @santiagobadia : Not sure we want to create the monomial machinery -Base.getindex(a::MonomialBasis,i::Integer) = Monomial() -Base.IndexStyle(::MonomialBasis) = IndexLinear() - -function testvalue(::Type{MonomialBasis{D,T}}) where {D,T} - MonomialBasis{D}(T,tfill(0,Val{D}()),CartesianIndex{D}[]) -end +isHierarchical(::Type{Monomial}) = true """ - MonomialBasis{D}(::Type{T}, orders::Tuple [, filter::Function]) where {D,T} + MonomialBasis{D,V} = CartProdPolyBasis{D,V,Monomial} -This version of the constructor allows to pass a tuple `orders` containing the -polynomial order to be used in each of the `D` dimensions in order to construct -an anisotropic tensor-product space. +Alias for cartesian product monomial basis, scalar valued or multi-valued. """ -function MonomialBasis{D}( - ::Type{T}, orders::NTuple{D,Int}, filter::Function=_q_filter) where {D,T} - - terms = _define_terms(filter, orders) - MonomialBasis{D}(T,orders,terms) -end +const MonomialBasis{D,V} = CartProdPolyBasis{D,V,Monomial} """ - MonomialBasis{D}(::Type{T}, order::Int [, filter::Function]) where {D,T} - -Returns an instance of `MonomialBasis` representing a multivariate polynomial basis -in `D` dimensions, of polynomial degree `order`, whose value is represented by the type `T`. -The type `T` is typically `<:Number`, e.g., `Float64` for scalar-valued functions and `VectorValue{D,Float64}` -for vector-valued ones. - -# Filter function - -The `filter` function is used to select which terms of the tensor product space -of order `order` in `D` dimensions are to be used. If the filter is not provided, the full tensor-product -space is used by default leading to a multivariate polynomial space of type Q. -The signature of the filter function is - - (e,order) -> Bool - -where `e` is a tuple of `D` integers containing the exponents of a multivariate monomial. The following filters -are used to select well known polynomial spaces - -- Q space: `(e,order) -> true` -- P space: `(e,order) -> sum(e) <= order` -- "Serendipity" space: `(e,order) -> sum( [ i for i in e if i>1 ] ) <= order` + MonomialBasis(::Val{D}, ::Type{V}, order::Int, terms::Vector) + MonomialBasis(::Val{D}, ::Type{V}, order::Int [, filter::Function]) + MonomialBasis(::Val{D}, ::Type{V}, orders::Tuple [, filter::Function]) +High level constructors of [`MonomialBasis`](@ref). """ -function MonomialBasis{D}( - ::Type{T}, order::Int, filter::Function=_q_filter) where {D,T} +MonomialBasis(args...) = CartProdPolyBasis(Monomial, args...) - orders = tfill(order,Val{D}()) - MonomialBasis{D}(T,orders,filter) -end - -# API - -""" - get_exponents(b::MonomialBasis) +# 1D evaluation implementation -Get a vector of tuples with the exponents of all the terms in the -monomial basis. - -# Examples - -```jldoctest -using Gridap.Polynomials - -b = MonomialBasis{2}(Float64,2) - -exponents = get_exponents(b) - -println(exponents) - -# output -Tuple{Int,Int}[(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2)] -``` -""" -function get_exponents(b::MonomialBasis) - indexbase = 1 - [Tuple(t) .- indexbase for t in b.terms] -end - -""" - get_order(b::MonomialBasis) -""" -function get_order(b::MonomialBasis) - maximum(b.orders) -end - -""" - get_orders(b::MonomialBasis) -""" -function get_orders(b::MonomialBasis) - b.orders -end - -""" -""" -return_type(::MonomialBasis{D,T}) where {D,T} = T - -# Field implementation -function return_cache(f::MonomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} - @check D == length(eltype(x)) "Incorrect number of point components" - zT = zero(T) - zxi = zero(eltype(eltype(x))) - Tp = typeof( zT*zxi*zxi + zT*zxi*zxi ) - np = length(x) - ndof = length(f) - n = 1 + _maximum(f.orders) - r = CachedArray(zeros(Tp,(np,ndof))) - v = CachedArray(zeros(Tp,(ndof,))) - c = CachedArray(zeros(eltype(Tp),(D,n))) - (r, v, c) -end - -function evaluate!(cache,f::MonomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} - r, v, c = cache - np = length(x) - ndof = length(f) - 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!(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,MonomialBasis{D,V}}, - x::AbstractVector{<:Point}, - ::Type{T}, - TisbitsType::Val{true}) where {D,V,T} - - f = fg.fa - @check D == length(eltype(x)) "Incorrect number of point components" - np = length(x) - ndof = length(f) - 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 _return_cache( - fg::FieldGradientArray{1,MonomialBasis{D,V}}, - x::AbstractVector{<:Point}, - ::Type{T}, - TisbitsType::Val{false}) where {D,V,T} - - cache = _return_cache(fg,x,T,Val{true}()) - z = CachedArray(zeros(eltype(T),D)) - (cache...,z) -end - -function return_cache( - fg::FieldGradientArray{1,MonomialBasis{D,V}}, - x::AbstractVector{<:Point}) where {D,V} - - xi = testitem(x) - T = gradient_type(V,xi) - TisbitsType = Val(isbitstype(T)) - _return_cache(fg,x,T,TisbitsType) -end - -function _evaluate!( - cache, - fg::FieldGradientArray{1,MonomialBasis{D,T}}, - x::AbstractVector{Tp}, - TisbitsType::Val{true}) where {D,T,Tp<:Point} - - f = fg.fa - r, v, c, g = cache - Tz = VectorValue{D,eltype(gradient_type(T,zero(Tp)))} - z = zero(Mutable(Tz)) - np = length(x) - ndof = length(f) - 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!(v,xi,f.orders,f.terms,c,g,z,T) - for j in 1:ndof - @inbounds r[i,j] = v[j] - end - end - r.array -end - -function _evaluate!( - cache, - fg::FieldGradientArray{1,MonomialBasis{D,T}}, - x::AbstractVector{<:Point}, - TisbitsType::Val{false}) where {D,T} +function _evaluate_1d!(::Type{Monomial},K::Int,c::AbstractMatrix{T},x,d) where T<:Number + n = K + 1 + xn = one(T) + @inbounds xd = x[d] - f = fg.fa - r, v, c, g, z = cache - np = length(x) - ndof = length(f) - 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!(v,xi,f.orders,f.terms,c,g,z,T) - for j in 1:ndof - @inbounds r[i,j] = v[j] - end + for i in 1:n + @inbounds c[d,i] = xn + xn *= xd end - r.array -end - -function evaluate!( - cache, - fg::FieldGradientArray{1,MonomialBasis{D,T}}, - x::AbstractVector{<:Point}) where {D,T} - - r, v, c, g = cache - TisbitsType = Val(isbitstype(eltype(c))) - _evaluate!(cache,fg,x,TisbitsType) -end - -function return_cache( - fg::FieldGradientArray{2,MonomialBasis{D,V}}, - x::AbstractVector{<:Point}) where {D,V} - - f = fg.fa - @check D == length(eltype(x)) "Incorrect number of point components" - np = length(x) - ndof = length(f) - 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,MonomialBasis{D,T}}, - x::AbstractVector{<:Point}) where {D,T} +function _gradient_1d!(::Type{Monomial},K,g::AbstractMatrix{T},x,d) where T<:Number + n = K + 1 + z = zero(T) + xn = one(T) + @inbounds xd = x[d] - f = fg.fa - r, v, c, g, h = cache - np = length(x) - ndof = length(f) - 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!(v,xi,f.orders,f.terms,c,g,h,T) - for j in 1:ndof - @inbounds r[i,j] = v[j] - end + @inbounds g[d,1] = z + for i in 2:n + @inbounds g[d,i] = (i-1)*xn + xn *= xd end - r.array -end - -# Optimizing evaluation at a single point - -function return_cache(f::AbstractVector{Monomial},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{Monomial},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{Monomial}}, 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{Monomial}}, 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 - -_q_filter(e,o) = true -function _define_terms(filter,orders) - t = orders .+ 1 - g = (0 .* orders) .+ 1 - cis = CartesianIndices(t) - co = CartesianIndex(g) - maxorder = _maximum(orders) - [ ci for ci in cis if filter(Int[Tuple(ci-co)...],maxorder) ] +function _hessian_1d!(::Type{Monomial},::Val{0},h::AbstractMatrix{T},x,d) where {T<:Number} end -function _evaluate_1d!(v::AbstractMatrix{T},x,order,d) where T - n = order + 1 - z = one(T) - @inbounds v[d,1] = z - @inbounds xd = x[d] - xn = xd - for i in 2:n - @inbounds v[d,i] = xn - xn *= xd +function _hessian_1d!(::Type{Monomial},K,h::AbstractMatrix{T},x,d) where T<:Number + if iszero(K) + @inbounds h[d,1] = zero(T) + return end -end -function _gradient_1d!(v::AbstractMatrix{T},x,order,d) where T - n = order + 1 + n = K + 1 # n>1 z = zero(T) - @inbounds v[d,1] = z - @inbounds xd = x[d] xn = one(T) - for i in 2:n - @inbounds v[d,i] = (i-1)*xn - xn *= xd - end -end - -function _hessian_1d!(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 - end @inbounds xd = x[d] - xn = one(T) + + @inbounds h[d,1] = z + @inbounds h[d,2] = z for i in 3:n - @inbounds v[d,i] = (i-1)*(i-2)*xn + @inbounds h[d,i] = (i-1)*(i-2)*xn xn *= xd end end -function _evaluate_nd!( - 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!(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 _set_value!(v::AbstractVector{V},s::T,k) where {V,T} - ncomp = num_indep_components(V) - z = zero(T) - @inbounds for j in 1:ncomp - v[k] = ntuple(i -> ifelse(i == j, s, z),Val(ncomp)) - k += 1 - end - k -end - -function _set_value!(v::AbstractVector{<:Real},s,k) - @inbounds v[k] = s - k+1 -end - -function _gradient_nd!( - v::AbstractVector{G}, - x, - orders, - terms::AbstractVector{CartesianIndex{D}}, - c::AbstractMatrix{T}, - g::AbstractMatrix{T}, - z::AbstractVector{T}, - ::Type{V}) where {G,T,D,V} - - dim = D - for d in 1:dim - _evaluate_1d!(c,x,orders[d],d) - _gradient_1d!(g,x,orders[d],d) - end - - 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) +# Optimizations for 0 to 1/2 derivatives at once +function _derivatives_1d!(::Type{Monomial},K,t::NTuple{2},x,d) + if K < 2 + @inline _evaluate_1d!(Monomial, K, t[1], x, d) + @inline _gradient_1d!(Monomial, K, t[2], x, d) + return end -end - -function _set_gradient!( - v::AbstractVector{G},s,k,::Type{<:Real}) where G - - @inbounds v[k] = s - k+1 -end - -@generated function  _set_gradient!( - v::AbstractVector{G},s,k,::Type{V}) where {V,G} - # Git blame me for readable non-generated version + @inbounds begin + n = K + 1 # n > 2 + v, g = t + T = eltype(v) - w = zero(V) - m = Array{String}(undef, size(G)) - N_val_dims = length(size(V)) - s_size = size(G)[1:end-N_val_dims] + z = zero(T) + xn = one(T) + xd = x[d] - body = "T = eltype(s); z = zero(T);" - for ci in CartesianIndices(s_size) - id = join(Tuple(ci)) - body *= "@inbounds s$id = s[$ci];" - end - - for j in CartesianIndices(w) - for i in CartesianIndices(m) - m[i] = "z" - end - for ci in CartesianIndices(s_size) - id = join(Tuple(ci)) - m[ci,j] = "s$id" + v[d,1] = xn + g[d,1] = z + for i in 2:n + g[d,i] = (i-1)*xn + xn *= xd + v[d,i] = xn end - body *= "@inbounds v[k] = ($(join(tuple(m...), ", ")));" - body *= "k = k + 1;" end - - body = Meta.parse(string("begin ",body," end")) - return Expr(:block, body ,:(return k)) end -# Specialization for SymTensorValue and SymTracelessTensorValue, -# necessary as long as outer(Point, V<:AbstractSymTensorValue)::G does not -# return a tensor type that implements the appropriate symmetries of the -# gradient (and hessian) -@generated function _set_gradient!( - v::AbstractVector{G},s,k,::Type{V}) where {V<:AbstractSymTensorValue{D},G} where D - # Git blame me for readable non-generated version - - T = eltype(s) - m = Array{String}(undef, size(G)) - s_length = size(G)[1] - - is_traceless = V <: SymTracelessTensorValue - skip_last_diagval = is_traceless ? 1 : 0 # Skid V_DD if traceless - - body = "z = $(zero(T));" - for i in 1:s_length - body *= "@inbounds s$i = s[$i];" - end - - for c in 1:(D-skip_last_diagval) # Go over cols - for r in c:D # Go over lower triangle, current col - for i in eachindex(m) - m[i] = "z" - end - for i in 1:s_length # indices of the Vector s - m[i,r,c] = "s$i" - if (r!=c) - m[i,c,r] = "s$i" - elseif is_traceless # V_rr contributes negatively to V_DD (tracelessness) - m[i,D,D] = "-s$i" - end - end - body *= "@inbounds v[k] = ($(join(tuple(m...), ", ")));" - body *= "k = k + 1;" - end +function _derivatives_1d!(::Type{Monomial},K,t::NTuple{3},x,d) + if K < 3 + @inline _evaluate_1d!(Monomial, K, t[1], x, d) + @inline _gradient_1d!(Monomial, K, t[2], x, d) + @inline _hessian_1d!( Monomial, K, t[3], x, d) + return end - body = Meta.parse(string("begin ",body," end")) - return Expr(:block, body ,:(return k)) -end - -function _hessian_nd!( - 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!(c,x,orders[d],d) - _gradient_1d!(g,x,orders[d],d) - _hessian_1d!(h,x,orders[d],d) - end + @inbounds begin + n = K + 1 # n > 2 + v, g, h = t + T = eltype(v) - z = zero(Mutable(TensorValue{D,D,T})) - o = one(T) - k = 1 + z = zero(T) + o = one(T) + xd = x[d] - for ci in terms + v[d,1] = o; g[d,1] = z; h[d,1] = z + v[d,2] = xd; g[d,2] = o; h[d,2] = z - s = z - for i in eachindex(s) - @inbounds s[i] = o + xn = xd + xnn = o + for i in 3:n + h[d,i] = (i-1)*xnn + xnn = (i-1)*xn + g[d,i] = xnn + xn *= xd + v[d,i] = xn 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 -_maximum(orders::Tuple{}) = 0 -_maximum(orders) = maximum(orders) diff --git a/src/Polynomials/NedelecPolyBases.jl b/src/Polynomials/NedelecPolyBases.jl new file mode 100644 index 000000000..39b4f8a73 --- /dev/null +++ b/src/Polynomials/NedelecPolyBases.jl @@ -0,0 +1,272 @@ +""" + NedelecPolyBasisOnSimplex{D,V,PT} <: PolynomialBasis{D,V,PT} + +Basis of the vector valued (`V<:VectorValue{D}`) space ℕ𝔻ᴰₙ(△) for `D`=2,3. +This space is the polynomial space for Nedelec elements on simplices with +curl in (ℙᴰₙ)ᴰ. Its maximum degree is n+1 = `K`. `get_order` on it returns `K`. + + ℕ𝔻ᴰₙ(△) = (ℙᴰₙ)ᴰ ⊕ x × (ℙᴰₙ \\ ℙᴰₙ₋₁)ᴰ + +Currently, the basis is implemented as the union of a CartProdPolyBasis{...,PT} +for ℙᴰₙ and a monomial basis for x × (ℙᴰₙ \\ ℙᴰₙ₋₁)ᴰ. + +!!! warning + Using this basis is not recommanded, [`BarycentricPmΛBasis`](@ref) is better + numerically conditioned for higher degrees, they are obtained by using + `Bernstein` as argument of [`FEEC_poly_basis`](@ref) . + +# Examples +These return instances of `NedelecPolyBasisOnSimplex` +```jldoctest +# a basis for Nedelec on triangles with curl in ℙ²₁ +b = FEEC_poly_basis(Val(2),Float64,2,1,:P⁻,Monomial) + +# a basis for Nedelec on tetrahedra with curl in ℙ³₁ +b = FEEC_poly_basis(Val(3),Float64,2,1,:P⁻,Monomial) +``` +""" +struct NedelecPolyBasisOnSimplex{D,V,PT} <: PolynomialBasis{D,V,PT} + order::Int + function NedelecPolyBasisOnSimplex{D}(::Type{PT},::Type{T},order::Integer) where {D,PT<:Polynomial,T} + @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value" + @check isconcretetype(PT) "PT needs to be a concrete <:Polynomial type" + @check isHierarchical(PT) "The polynomial basis must be hierarchical for this space." + @notimplementedif !(D in (2,3)) + V = VectorValue{D,T} + new{D,V,PT}(Int(order)) + end +end + +get_order(f::NedelecPolyBasisOnSimplex) = f.order + 1 # Return actual maximum poly order +get_orders(b::NedelecPolyBasisOnSimplex{D}) where D = tfill(get_order(b), Val(D)) + +function Base.size(f::NedelecPolyBasisOnSimplex{D}) where D + K = get_order(f) + n = div(K*prod(i->(K+i),2:D),factorial(D-1)) + (n,) +end + +function testvalue(::Type{NedelecPolyBasisOnSimplex{D,V,PT}}) where {D,V,PT} + T = eltype(V) + NedelecPolyBasisOnSimplex{D}(PT,T,0) +end + +function return_cache( + f::NedelecPolyBasisOnSimplex{D,V,PT},x::AbstractVector{<:Point}) where {D,V,PT} + + K = get_order(f) + np = length(x) + ndofs = length(f) + Vr = _return_val_eltype(f,x) + a = zeros(Vr,(np,ndofs)) + P = CartProdPolyBasis(PT,Val(D),V,K-1,_p_filter) + cP = return_cache(P,x) + CachedArray(a), cP, P +end + +function evaluate!( + cache,f::NedelecPolyBasisOnSimplex{3,V},x::AbstractVector{<:Point}) where V + ca,cP,P = cache + K = get_order(f) + np = length(x) + ndofs = length(f) + ndofsP = length(P) + setsize!(ca,(np,ndofs)) + Px = evaluate!(cP,P,x) + a = ca.array + T = eltype(V) + z = zero(T) + for (i,xi) in enumerate(x) + # terms for (ℙₖ)³ + for j in 1:ndofsP + a[i,j] = Px[i,j] + end + # terms for x × (ℙₖ\ℙₖ₋₁)³ + j = ndofsP + x1,x2,x3 = x[i] + for β in 1:K + for α in 1:(K+1-β) + j += 1 + a[i,j] = VectorValue( + -x1^(α-1)*x2^(K-α-β+2)*x3^(β-1), + x1^α*x2^(K-α-β+1)*x3^(β-1), + z) + j += 1 + a[i,j] = VectorValue( + -x1^(K-α-β+1)*x2^(β-1)*x3^α, + z, + x1^(K-α-β+2)*x2^(β-1)*x3^(α-1)) + end + end + for γ in 1:K + j += 1 + a[i,j] = VectorValue( + z, + -x2^(γ-1)*x3^(K-γ+1), + x2^γ*x3^(K-γ)) + end + end + a +end + +function evaluate!( + cache,f::NedelecPolyBasisOnSimplex{2,V},x::AbstractVector{<:Point}) where V + ca,cP,P = cache + K = get_order(f) + np = length(x) + ndofs = length(f) + ndofsP = length(P) + setsize!(ca,(np,ndofs)) + a = ca.array + T = eltype(V) + z = zero(T) + Px = evaluate!(cP,P,x) + for (i,xi) in enumerate(x) + # terms for (ℙₖ)² + for j in 1:ndofsP + a[i,j] = Px[i,j] + end + # terms for x × (ℙₖ\ℙₖ₋₁)² + j = ndofsP + x1,x2 = xi + for α in 1:K + j += 1 + a[i,j] = VectorValue(-x1^(α-1)*x2^(K-α+1),x1^α*x2^(K-α)) + end + #u = one(T) + #a[i,1] = VectorValue((u,z)) + #a[i,2] = VectorValue((z,u)) + #a[i,3] = VectorValue((-xi[2],xi[1])) + end + a +end + +function return_cache( + g::FieldGradientArray{1,<:NedelecPolyBasisOnSimplex{D,V,PT}}, + x::AbstractVector{<:Point}) where {D,V,PT} + f = g.fa + K = get_order(f) + np = length(x) + ndofs = length(f) + xi = testitem(x) + Vr = _return_val_eltype(f,x) + G = gradient_type(Vr,xi) + a = zeros(G,(np,ndofs)) + mb = CartProdPolyBasis(PT,Val(D),V,K-1,_p_filter) + P = Broadcasting(∇)(mb) + cP = return_cache(P,x) + CachedArray(a), cP, P +end + +function evaluate!( + cache, + g::FieldGradientArray{1,<:NedelecPolyBasisOnSimplex{3,V}}, + x::AbstractVector{<:Point}) where V + ca,cP,P = cache + f = g.fa + K = get_order(f) + np = length(x) + ndofs = length(f) + setsize!(ca,(np,ndofs)) + a = ca.array + fill!(a,zero(eltype(a))) + ndofsP = length(P) + Px = evaluate!(cP,P,x) + T = eltype(V) + z = zero(T) + for (i,xi) in enumerate(x) + # terms for ∇((ℙₖ)³) + for j in 1:ndofsP + a[i,j] = Px[i,j] + end + # terms for ∇(x × (ℙₖ\ℙₖ₋₁)³) + j = ndofsP + x1,x2,x3 = x[i] + for β in 1:K + for α in 1:(K+1-β) + j += 1 + a[i,j] = TensorValue( + #-x1^(α-1)*x2^(K-α-β+2)*x3^(β-1), + -(α-1)*_exp(x1,α-2)*x2^(K-α-β+2)*x3^(β-1), + -x1^(α-1)*(K-α-β+2)*_exp(x2,K-α-β+1)*x3^(β-1), + -x1^(α-1)*x2^(K-α-β+2)*(β-1)*_exp(x3,β-2), + #x1^α*x2^(K-α-β+1)*x3^(β-1), + α*_exp(x1,α-1)*x2^(K-α-β+1)*x3^(β-1), + x1^α*(K-α-β+1)*_exp(x2,K-α-β)*x3^(β-1), + x1^α*x2^(K-α-β+1)*(β-1)*_exp(x3,β-2), + z,z,z) + j += 1 + a[i,j] = TensorValue( + #-x1^(K-α-β+1)*x2^(β-1)*x3^α, + -(K-α-β+1)*_exp(x1,K-α-β)*x2^(β-1)*x3^α, + -x1^(K-α-β+1)*(β-1)*_exp(x2,β-2)*x3^α, + -x1^(K-α-β+1)*x2^(β-1)*α*_exp(x3,α-1), + z,z,z, + #x1^(K-α-β+2)*x2^(β-1)*x3^(α-1), + (K-α-β+2)*_exp(x1,K-α-β+1)*x2^(β-1)*x3^(α-1), + x1^(K-α-β+2)*(β-1)*_exp(x2,β-2)*x3^(α-1), + x1^(K-α-β+2)*x2^(β-1)*(α-1)*_exp(x3,α-2)) + end + end + for γ in 1:K + j += 1 + a[i,j] = TensorValue( + z,z,z, + #-x2^(γ-1)*x3^(K-γ+1), + -0*x2^(γ-1)*x3^(K-γ+1), + -(γ-1)*_exp(x2,γ-2)*x3^(K-γ+1), + -x2^(γ-1)*(K-γ+1)*_exp(x3,K-γ), + #x2^γ*x3^(K-γ), + 0*x2^γ*x3^(K-γ), + γ*_exp(x2,γ-1)*x3^(K-γ), + x2^γ*(K-γ)*_exp(x3,K-γ-1)) + end + #u = one(T) + #a[i,4] = TensorValue((z,-u,z, u,z,z, z,z,z)) + #a[i,5] = TensorValue((z,z,-u, z,z,z, u,z,z)) + #a[i,6] = TensorValue((z,z,z, z,z,-u, z,u,z)) + end + a +end + +_exp(a,y) = y>0 ? a^y : one(a) + +function evaluate!( + cache, + g::FieldGradientArray{1,<:NedelecPolyBasisOnSimplex{2,V}}, + x::AbstractVector{<:Point}) where V + f = g.fa + ca,cP,P = cache + K = get_order(f) + np = length(x) + ndofs = length(f) + setsize!(ca,(np,ndofs)) + a = ca.array + fill!(a,zero(eltype(a))) + T = eltype(V) + z = zero(T) + ndofsP = length(P) + Px = evaluate!(cP,P,x) + for (i,xi) in enumerate(x) + # terms for ∇((ℙₖ)²) + for j in 1:ndofsP + a[i,j] = Px[i,j] + end + # terms for ∇(x × (ℙₖ\ℙₖ₋₁)²) + j = ndofsP + x1,x2 = x[i] + for α in 1:K + j += 1 + a[i,j] = TensorValue( + #-x1^(α-1)*x2^(K-α+1), + -(α-1)*_exp(x1,α-2)*x2^(K-α+1), + -x1^(α-1)*(K-α+1)*_exp(x2,K-α), + #x1^α*x2^(K-α), + α*_exp(x1,α-1)*x2^(K-α), + x1^α*(K-α)*_exp(x2,K-α-1)) + end + #u = one(T) + #a[i,3] = TensorValue((z,-u, u,z)) + end + a +end diff --git a/src/Polynomials/PCurlGradMonomialBases.jl b/src/Polynomials/PCurlGradMonomialBases.jl deleted file mode 100644 index 23c6d0f21..000000000 --- a/src/Polynomials/PCurlGradMonomialBases.jl +++ /dev/null @@ -1,294 +0,0 @@ - -""" -struct PCurlGradMonomialBasis{...} <: AbstractArray{Monomial} - -This type implements a multivariate vector-valued polynomial basis -spanning the space needed for Raviart-Thomas reference elements on simplices. -The type parameters and fields of this `struct` are not public. -This type fully implements the [`Field`](@ref) interface, with up to first order -derivatives. -""" -struct PCurlGradMonomialBasis{D,T} <: AbstractVector{Monomial} - order::Int - pterms::Array{CartesianIndex{D},1} - sterms::Array{CartesianIndex{D},1} - perms::Matrix{Int} - function PCurlGradMonomialBasis(::Type{T},order::Int, - pterms::Array{CartesianIndex{D},1},sterms::Array{CartesianIndex{D},1}, - perms::Matrix{Int}) where {D,T} - new{D,T}(order,pterms,sterms,perms) - end -end - -Base.size(a::PCurlGradMonomialBasis) = (_ndofs_pgrad(a),) -# @santiagobadia : Not sure we want to create the monomial machinery -Base.getindex(a::PCurlGradMonomialBasis,i::Integer) = Monomial() -Base.IndexStyle(::PCurlGradMonomialBasis) = IndexLinear() - -""" -PCurlGradMonomialBasis{D}(::Type{T},order::Int) where {D,T} - -Returns a `PCurlGradMonomialBasis` object. `D` is the dimension -of the coordinate space and `T` is the type of the components in the vector-value. -The `order` argument has the following meaning: the divergence of the functions -in this basis is in the P space of degree `order`. -""" -function PCurlGradMonomialBasis{D}(::Type{T},order::Int) where {D,T} - @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value" - P_k = MonomialBasis{D}(T, order, _p_filter) - S_k = MonomialBasis{D}(T, order, _s_filter) - pterms = P_k.terms - sterms = S_k.terms - perms = _prepare_perms(D) - PCurlGradMonomialBasis(T,order,pterms,sterms,perms) -end - -""" - num_terms(f::PCurlGradMonomialBasis{D,T}) where {D,T} -""" -function num_terms(f::PCurlGradMonomialBasis{D,T}) where {D,T} - Int(_p_dim(f.order,D)*D + _p_dim(f.order,D-1)) -end - -get_order(f::PCurlGradMonomialBasis{D,T}) where {D,T} = f.order - -return_type(::PCurlGradMonomialBasis{D,T}) where {D,T} = T - -function return_cache(f::PCurlGradMonomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} - @check D == length(eltype(x)) "Incorrect number of point components" - np = length(x) - ndof = _ndofs_pgrad(f) - n = 1 + f.order+1 - V = VectorValue{D,T} - r = CachedArray(zeros(V,(np,ndof))) - v = CachedArray(zeros(V,(ndof,))) - c = CachedArray(zeros(T,(D,n))) - (r, v, c) -end - -function evaluate!(cache,f::PCurlGradMonomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} - r, v, c = cache - np = length(x) - ndof = _ndofs_pgrad(f) - n = 1 + f.order+1 - setsize!(r,(np,ndof)) - setsize!(v,(ndof,)) - setsize!(c,(D,n)) - for i in 1:np - @inbounds xi = x[i] - _evaluate_nd_pcurlgrad!(v,xi,f.order+1,f.pterms,f.sterms,f.perms,c) - for j in 1:ndof - @inbounds r[i,j] = v[j] - end - end - r.array -end - -function return_cache( - fg::FieldGradientArray{1,PCurlGradMonomialBasis{D,T}}, - x::AbstractVector{<:Point}) where {D,T} - - f = fg.fa - @check D == length(eltype(x)) "Incorrect number of point components" - np = length(x) - ndof = _ndofs_pgrad(f) - n = 1 + f.order+1 - xi = testitem(x) - V = VectorValue{D,T} - G = gradient_type(V,xi) - r = CachedArray(zeros(G,(np,ndof))) - v = CachedArray(zeros(G,(ndof,))) - c = CachedArray(zeros(T,(D,n))) - g = CachedArray(zeros(T,(D,n))) - (r, v, c, g) -end - -function evaluate!(cache, - fg::FieldGradientArray{1,PCurlGradMonomialBasis{D,T}}, - x::AbstractVector{<:Point}) where {D,T} - - f = fg.fa - r, v, c, g = cache - np = length(x) - ndof = _ndofs_pgrad(f) - n = 1 + f.order+1 - setsize!(r,(np,ndof)) - setsize!(v,(ndof,)) - setsize!(c,(D,n)) - setsize!(g,(D,n)) - V = VectorValue{D,T} - for i in 1:np - @inbounds xi = x[i] - _gradient_nd_pcurlgrad!(v,xi,f.order+1,f.pterms,f.sterms,f.perms,c,g,V) - for j in 1:ndof - @inbounds r[i,j] = v[j] - end - end - r.array -end - - -# Helpers - -_p_filter(e,order) = (sum(e) <= order) -_s_filter(e,order) = (sum(e) == order) - -function _p_dim(order,D) - dim = 1 - for d in 1:D - dim *= order+d - end - dim/factorial(D) -end - -_ndofs_pgrad(f::PCurlGradMonomialBasis{D}) where D = num_terms(f) - - -function _evaluate_nd_pcurlgrad!( - v::AbstractVector{V}, - x, - order, - pterms::Array{CartesianIndex{D},1}, - sterms::Array{CartesianIndex{D},1}, - perms::Matrix{Int}, - c::AbstractMatrix{T}) where {V,T,D} - - dim = D - for d in 1:dim - _evaluate_1d!(c,x,order,d) - end - - o = one(T) - k = 1 - m = zero(Mutable(V)) - js = eachindex(m) - z = zero(T) - - for ci in pterms - for j in js - - @inbounds for i in js - m[i] = z - end - - s = o - @inbounds for d in 1:dim - s *= c[d,ci[perms[d,j]]] - end - - m[j] = s - v[k] = m - k += 1 - end - end - - for ci in sterms - @inbounds for i in js - m[i] = z - end - for j in js - - s = c[j,2] - @inbounds for d in 1:dim - s *= c[d,ci[d]] - end - - m[j] = s - - end - v[k] = m - k += 1 - end -end - -function _gradient_nd_pcurlgrad!( - v::AbstractVector{G}, - x, - order, - pterms::Array{CartesianIndex{D},1}, - sterms::Array{CartesianIndex{D},1}, - perms::Matrix{Int}, - c::AbstractMatrix{T}, - g::AbstractMatrix{T}, - ::Type{V}) where {G,T,D,V} - - dim = D - for d in 1:dim - _evaluate_1d!(c,x,order,d) - _gradient_1d!(g,x,order,d) - end - - z = zero(Mutable(V)) - m = zero(Mutable(G)) - js = eachindex(z) - mjs = eachindex(m) - o = one(T) - zi = zero(T) - k = 1 - - for ci in pterms - for j in js - - s = z - for i in js - s[i] = o - end - - for q in 1:dim - for d in 1:dim - if d != q - @inbounds s[q] *= c[d,ci[perms[d,j]]] - else - @inbounds s[q] *= g[d,ci[perms[d,j]]] - end - end - end - - @inbounds for i in mjs - m[i] = zi - end - - for i in js - @inbounds m[i,j] = s[i] - end - @inbounds v[k] = m - k += 1 - end - end - - for ci in sterms - - @inbounds for i in mjs - m[i] = zi - end - - for j in js - - s = z - for i in js - s[i] = c[j,2] - 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 - aux = o - @inbounds for d in 1:dim - aux *= c[d,ci[d]] - end - s[j] += aux - - for i in js - @inbounds m[i,j] = s[i] - end - end - @inbounds v[k] = m - k += 1 - end -end diff --git a/src/Polynomials/PolynomialInterfaces.jl b/src/Polynomials/PolynomialInterfaces.jl new file mode 100644 index 000000000..ec6fea0b4 --- /dev/null +++ b/src/Polynomials/PolynomialInterfaces.jl @@ -0,0 +1,410 @@ +############################ +# Polynomial family/types # +############################ + +""" + Polynomial <: Field + +Abstract type for polynomial bases families/types. It has trait +[`isHierarchical`](@ref). +""" +abstract type Polynomial <: Field end + +""" + isHierarchical(::Type{Polynomial})::Bool + +Return `true` if the 1D basis of order `K` of the given [`Polynomial`](@ref) +basis family is the union of the basis of order `K-1` and an other order `K` +polynomial. This implies that the iᵗʰ basis polynomial is of order i-1. + +The currently implemented hierarchical families are [Monomial](@ref), +[Legendre](@ref) and [Chebyshev](@ref). +""" +isHierarchical(::Type{<:Polynomial}) = @abstractmethod + +testvalue(::Type{PT}) where PT<:Polynomial = isconcretetype(PT) ? PT() : @abstractmethod + +########################################### +# Polynomial basis abstract type and APIs # +########################################### + +# Notations: +# +# D: spatial / input space dimension +# T: scalar type (Float64, ...) +# V: concrete type of image values (T, VectorValue{D,T} etc.) +# G: concrete MultiValue type holding the gradient or hessian of a function of +# value V, i.e. gradient_type(V,Point{D}) +# or gradient_type(gradient_type(V,p::Point{D}), p) +# +# PT: a concrete `Polynomial` type +# K: integer polynomial order (maximum order of any component and in any direction in nD). +# np: number of points at which a basis is evaluated +# ndof: number of basis polynomials +# ndof_1d: maximum number of 1D monomial in any spatial dimension + +""" + PolynomialBasis{D,V,PT<:Polynomial} <: AbstractVector{PT} + +Abstract type representing a generic multivariate polynomial basis. +The parameters are: +- `D`: the spatial dimension +- `V`: the image values type, a concrete type `<:Real` or `<:MultiValue` +- `PT <: Polynomial`: the family of the basis polynomials (must be a concrete type). + +The implementations also stores `K`: the maximum order of a basis polynomial in a spatial component +""" +abstract type PolynomialBasis{D,V,PT<:Polynomial} <: AbstractVector{PT} end + +@inline Base.size(::PolynomialBasis{D,V}) where {D,V} = @abstractmethod +@inline Base.getindex(::PolynomialBasis{D,V,PT}, i::Integer) where {D,V,PT} = PT() +@inline Base.IndexStyle(::PolynomialBasis) = IndexLinear() +@inline return_type(::PolynomialBasis{D,V}) where {D,V} = V +return_type(b::LinearCombinationFieldVector{W,<:PolynomialBasis}) where W = return_type(b.fields) + + +""" + get_dimension(::PolynomialBasis{D}) -> D +""" +@inline get_dimension(::PolynomialBasis{D}) where D = D + +""" + get_order(b::PolynomialBasis) + +Return the maximum polynomial order in any dimension, or `0` in 0D. +For tensor-valued bases, it is the maximum for each component. +""" +@inline get_order(::PolynomialBasis) = @abstractmethod +get_order(f::LinearCombinationFieldVector) = get_order(f.fields) +get_order(f::AbstractVector{<:ConstantField}) = 0 + +""" + get_orders(b::PolynomialBasis{D}) + +Return the `D`-tuple of maximum polynomial orders in each spatial dimension, +or `()` in 0D. + +For tensor-valued bases, it is the maximum order of any component, for each dimension. +""" +get_orders(::PolynomialBasis) = @abstractmethod + +testvalue(::Type{<:PolynomialBasis}) = @abstractmethod + + +########### +# Helpers # +########### + +_q_filter( e,order) = (maximum(e,init=0) <= order) # ℚₙ +_qh_filter(e,order) = (maximum(e,init=0) == order) # ℚ̃ₙ = ℚₙ\ℚ₍ₙ₋₁₎ +_p_filter( e,order) = (sum(e) <= order) # ℙₙ +_ph_filter(e,order) = (sum(e) == order) # ℙ̃ₙ = ℙₙ\ℙ₍ₙ₋₁₎ +_ser_filter(e,order) = (sum( [ i for i in e if i>1 ] ) <= order) # Serendipity + +function _define_terms(filter,orders) + t = orders .+ 1 + g = (0 .* orders) .+ 1 + cis = CartesianIndices(t) + co = CartesianIndex(g) + maxorder = maximum(orders, init=0) + [ ci for ci in cis if filter(Int[Tuple(ci-co)...],maxorder) ] +end + + +######################################### +# Generic array of field implementation # +######################################### + +function _return_cache( + f::PolynomialBasis{D}, x,::Type{G},::Val{N_deriv}) where {D,G,N_deriv} + + T = eltype(G) + np = length(x) + ndof = length(f) + ndof_1d = get_order(f) + 1 + # Cache for the returned array + r = CachedArray(zeros(G,(np,ndof))) + # Mutable cache for one N_deriv's derivative of a T-valued scalar polynomial + s = MArray{Tuple{Vararg{D,N_deriv}},T}(undef) + # Cache for the 1D basis function values in each dimension (to be + # tensor-producted), and of their N_deriv'th 1D derivatives + t = ntuple( _ -> CachedArray(zeros(T,(D,ndof_1d))), Val(N_deriv+1)) + (r, s, t...) +end + +function _return_val_eltype(b::PolynomialBasis{D,V}, x::AbstractVector{<:Point}) where {D,V} + xi = testitem(x) + zVc = zero(eltype(V)) + zxic = zero(eltype(xi)) + T = typeof(zVc*zxic) + change_eltype(V, T) # Necessary for dual number probagation for autodiff +end + +function return_cache(f::PolynomialBasis{D,V}, x::AbstractVector{<:Point}) where {D,V} + @assert D == length(eltype(x)) "Incorrect number of point components" + Vr = _return_val_eltype(f,x) + _return_cache(f,x,Vr,Val(0)) +end + +function return_cache( + fg::FieldGradientArray{N,<:PolynomialBasis{D,V}}, + x::AbstractVector{<:Point}) where {N,D,V} + + @assert D == length(eltype(x)) "Incorrect number of point components" + f = fg.fa + xi = testitem(x) + G = _return_val_eltype(f,x) + for _ in 1:N + G = gradient_type(G,xi) + end + _return_cache(f,x,G,Val(N)) +end + + +function _setsize!(f::PolynomialBasis{D}, np, r, t...) where D + ndof = length(f) + ndof_1d = get_order(f) + 1 + setsize!(r,(np,ndof)) + for c in t + setsize!(c,(D,ndof_1d)) + end +end + +""" + _get_static_parameters(::PolynomialBasis) + +Return a (tuple of) static parameter(s) appended to low level `[...]_nd!` evaluation +calls, default is `Val(get_order(b))`. +""" +_get_static_parameters(b::PolynomialBasis) = Val(get_order(b)) + +function evaluate!(cache, + f::PolynomialBasis, + x::AbstractVector{<:Point}) + + r, _, c = cache + np = length(x) + _setsize!(f,np,r,c) + params = _get_static_parameters(f) + _loop_point_evals!(np,x,f,r,c,params) + r.array +end + +# this is necessary to force inference of params and minimize runtime dispatches +# to only one (the dispatch on this method) +@noinline function _loop_point_evals!(np,x,f,r,c,params) + for i in 1:np + @inbounds xi = x[i] + _evaluate_nd!(f,xi,r,i,c,params) + end + nothing +end + + +function evaluate!(cache, + fg::FieldGradientArray{1,<:PolynomialBasis{D,V}}, + x::AbstractVector{<:Point}) where {D,V} + + f = fg.fa + r, s, c, g = cache + np = length(x) + _setsize!(f,np,r,c,g) + params = _get_static_parameters(f) + _loop_point_grads!(np,x,f,r,c,g,s,params) + r.array +end + +@noinline function _loop_point_grads!(np,x,f,r,c,g,s,params) + for i in 1:np + @inbounds xi = x[i] + _gradient_nd!(f,xi,r,i,c,g,s,params) + end + nothing +end + +function evaluate!(cache, + fg::FieldGradientArray{2,<:PolynomialBasis{D,V}}, + x::AbstractVector{<:Point}) where {D,V} + + f = fg.fa + r, s, c, g, h = cache + np = length(x) + _setsize!(f,np,r,c,g,h) + params = _get_static_parameters(f) + _loop_point_hess!(np,x,f,r,c,g,h,s,params) + r.array +end + +@noinline function _loop_point_hess!(np,x,f,r,c,g,h,s,params) + for i in 1:np + @inbounds xi = x[i] + _hessian_nd!(f,xi,r,i,c,g,h,s,params) + end + nothing +end + + +############################################## +# Optimizing of evaluation at a single point # +############################################## + +function return_cache(f::PolynomialBasis,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::PolynomialBasis,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,<:PolynomialBasis}, 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,<:PolynomialBasis}, 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 + + +############################### +# nD internal polynomial APIs # +############################### + +""" + _evaluate_nd!(b,xi,r,i,c,params) + +Compute and assign: `r`[`i`] = `b`(`xi`) = (`b`₁(`xi`), ..., `b`ₙ(`xi`)) + +where n = length(`b`) (cardinal of the basis), that is the function computes +the basis polynomials at a single point `xi` and sets the result in the `i`th +row of `r`. + +- `c` is an implementation specific cache for temporary computation of `b`(`xi`). +- `params` is an optional (tuple of) parameter(s) returned by [`_get_static_parameters(b)`](@ref _get_static_parameters) +""" +function _evaluate_nd!(b::PolynomialBasis, xi, r::AbstractMatrix, i, c, params) + @abstractmethod +end + +""" + _gradient_nd!(b,xi,r,i,c,g,s,params) + +Compute and assign: `r`[`i`] = ∇`b`(`xi`) = (∇`b`₁(`xi`), ..., ∇`b`ₙ(`xi`)) + +where n = length(`b`) (cardinal of the basis), like [`_evaluate_nd!`](@ref) but +for gradients of `b`ₖ(`xi`), and + +- `g` is an implementation specific cache for temporary computation of `∇b`(`xi`). +- `s` is a mutable length `D` cache for ∇`b`ₖ(`xi`). +""" +function _gradient_nd!(b::PolynomialBasis, xi, r::AbstractMatrix, i, c, g, s::MVector, params) + @abstractmethod +end + +""" + _hessian_nd!(b,xi,r,i,c,g,h,s,params) + +Compute and assign: `r`[`i`] = H`b`(`xi`) = (H`b`₁(`xi`), ..., H`b`ₙ(`xi`)) + +where n = length(`b`) (cardinal of the basis), like [`_evaluate_nd!`](@ref) but +for hessian matrices/tensor of `b`ₖ(`xi`), and + +- `h` is an implementation specific cache for temporary computation of `∇∇b`(`xi`). +- `s` is a mutable `D`×`D` cache for H`b`ₖ(`xi`). +""" +function _hessian_nd!(b::PolynomialBasis, xi, r::AbstractMatrix, i, c, g, h, s::MMatrix, params) + @abstractmethod +end + + +############################### +# 1D internal polynomial APIs # +############################### + +""" + _evaluate_1d!(PT::Type{<:Polynomial},K,c,x,d) + +Evaluates in place the 1D basis polynomials of the family `PT` at one D-dim. +point `x` along the given coordinate 1 ≤ `d` ≤ D. + +`c` is an AbstractMatrix of size (at least) `d`×(`K`+1), such that the +1 ≤ i ≤ `k`+1 values are stored in `c[d,i]`. +""" +function _evaluate_1d!(::Type{<:Polynomial},K,c,x,d) + @abstractmethod +end + +""" + _gradient_1d!(PT::Type{<:Polynomial},K,g,x,d) + +Like [`_evaluate_1d!`](@ref), but computes the first derivative of the basis +polynomials. +""" +function _gradient_1d!(::Type{<:Polynomial},K,g,x,d) + @abstractmethod +end + +""" + _hessian_1d!(PT::Type{<:Polynomial},K,g,x,d) + +Like [`_evaluate_1d!`](@ref), but computes the second derivative of the basis +polynomials. +""" +function _hessian_1d!(::Type{<:Polynomial},K,h::AbstractMatrix{T},x,d) where T<:Number + @abstractmethod +end + +""" + _derivatives_1d!(PT::Type{<:Polynomial}, K, (c,g,...), x, d) + +Same as calling +``` +_evaluate_1d!(PT, K, c, x d) +_gradient_1d!(PT, K, g, x d) + ⋮ +``` +but with performance optimization if implemented. +""" +function _derivatives_1d!( ::Type{<:Polynomial},K,t::NTuple{N},x,d) where N + @abstractmethod +end + +function _derivatives_1d!(PT::Type{<:Polynomial},K,t::NTuple{1},x,d) + @inline _evaluate_1d!(PT, K, t[1], x, d) +end + +function _derivatives_1d!(PT::Type{<:Polynomial},K,t::NTuple{2},x,d) + @inline _evaluate_1d!(PT, K, t[1], x, d) + @inline _gradient_1d!(PT, K, t[2], x, d) +end + +function _derivatives_1d!(PT::Type{<:Polynomial},K,t::NTuple{3},x,d) + @inline _evaluate_1d!(PT, K, t[1], x, d) + @inline _gradient_1d!(PT, K, t[2], x, d) + @inline _hessian_1d!( PT, K, t[3], x, d) +end diff --git a/src/Polynomials/Polynomials.jl b/src/Polynomials/Polynomials.jl index f07ff3784..a1d494ffe 100644 --- a/src/Polynomials/Polynomials.jl +++ b/src/Polynomials/Polynomials.jl @@ -1,14 +1,91 @@ """ -This module provides a collection of multivariate polynomial bases. +This module provides a collection of uni- and multi-variate scalar- and multi-value'd polynomial bases. -The exported names are: +Most of the basis polynomials are composed using products of 1D polynomials, +represented by the type [`Polynomial`](@ref). +Five `Polynomial` families are currently implemented: [`Monomial`](@ref), +[`Legendre`](@ref), [`Chebyshev`](@ref), [`Bernstein`](@ref) and [`ModalC0`](@ref). -$(EXPORTS) +The polynomial bases all subtype [`PolynomialBasis`](@ref), which subtypes +`AbstractVector{<:Field}`, so they implement the `Field` interface up to first +or second derivatives. + +Constructors for commonly used bases (see the documentation for the spaces definitions): +- ℚ spaces: `[Polynomial]Basis(Val(D), V, order)` +- ℙ spaces: `[Polynomial]Basis(..., Polynomials._p_filter)` +- 𝕊r spaces: `[Polynomial]Basis(..., Polynomials._ser_filter)` +- ℚ̃ spaces: `[Polynomial]Basis(..., Polynomials._qh_filter)` +- ℙ̃ spaces: `[Polynomial]Basis(..., Polynomials._ph_filter)` + +For bases for the Nélélec, Raviart-Thomas and BDM element spaces, use +[`FEEC_poly_basis`](@ref) with the arguments found in the +[ReferenceFEs summary](@ref "Reference FE summary") of the documentation. + +### Examples + +```julia +using Gridap +using Gridap.Polynomials +using Gridap.Fields: return_type + +# Basis of ℚ¹₂ of Float64 value type based on Bernstein polynomials: +# {(1-x)², 2x(1-x), x²} +D = 1; n = 2 # spatial dimension and order +b = BernsteinBasis(Val(D), Float64, n) + +# APIs +length(b) # 3 +return_type(b) # Float64 +get_order(b) # 2 + +xi =Point(0.1) +evaluate(b, xi) +evaluate(Broadcasting(∇)(b), xi) # gradients +evaluate(Broadcasting(∇∇)(b), xi) # hessians, not all basis support hessians +evaluate(b, [xi, xi]) # evaluation on arrays of points + +# Basis of ℚ²₂ of Float64 value type based on Legendre polynomials, our 1D +# Legendre polynomials are normalized for L2 scalar product and moved from +# [-1,1] to [0,1] using the change of variable x -> 2x-1 +# { 1, √3(2x-1), √5(6x²-6x+2), +# √3(2y-1), √3(2x-1)√3(2y-1), √5(6x²-6x+2)√3(2y-1), +# √5(6y²-6y+2), √3(2x-1)√5(6x²-6x+2), √5(6x²-6x+2)√5(6y²-6y+2) } +D = 2; n = 2 +b = LegendreBasis(Val(D), Float64, n) + +# Basis of (ℙ³₁)³ of VectorValue{3,Float64} value type, based on monomials: +# {(1,0,0), (0,1,0), (0,0,1) +# (x,0,0), (0,x,0), (0,0,x) +# (y,0,0), (0,y,0), (0,0,y) +# (z,0,0), (0,z,0), (0,0,z)} +D = 3; n = 1 +b = MonomialBasis(Val(D), VectorValue{D,Float64}, n, Polynomials._p_filter) +evaluate(b, Point(.1, .2, .3) + +# a basis for Nedelec on tetrahedra with curl in ℙ³₂ +D, k, r = 3, 1, 2+1 +b = FEEC_poly_basis(Val(D),Float64,r,k,:P⁻) # basis of order 3 + +# a basis for Nedelec on hexahedra with curl in ℚ³₁ +D, k, r = 3, 1, 1+1 +b = FEEC_poly_basis(Val(D),Float64,r,k,:Q⁻) # basis of order 2 + +# a basis for Raviart-Thomas on quadrilateral with divergence in ℚ₁ +D, k, r = 2, 2-1, 1+1 +b = FEEC_poly_basis(Val(D),Float64,r,k,:Q⁻; rotate_90=true) # basis of order 3 + +# a basis for Raviart-Thomas on tetrahedra with divergence in ℙ₂ +D, k, r = 3, 3-1, 2+1 +b = FEEC_poly_basis(Val(D),Float64,r,k,:P⁻) # basis of order 3 +``` + +$(public_names_in_md(@__MODULE__)) """ module Polynomials using DocStringExtensions using LinearAlgebra: mul! +using LinearAlgebra: I using StaticArrays using Gridap.Helpers using Gridap.Arrays @@ -16,34 +93,81 @@ using Gridap.TensorValues using Gridap.Fields using PolynomialBases: jacobi, jacobi_and_derivative +using Combinatorics: multiexponents, multinomial, combinations +using Base.Iterators: take +using Base: @propagate_inbounds +using Gridap.Fields: LinearCombinationFieldVector import Gridap.Fields: evaluate! import Gridap.Fields: return_cache import Gridap.Arrays: return_type import Gridap.Arrays: testvalue -export MonomialBasis -export QGradMonomialBasis -export QCurlGradMonomialBasis -export PCurlGradMonomialBasis -export ModalC0Basis -export JacobiPolynomialBasis -export get_exponents +export Polynomial +export isHierarchical +export PolynomialBasis export get_order +export get_dimension + +export CartProdPolyBasis +export get_exponents export get_orders -export num_terms +export Monomial +export MonomialBasis +export LegendreBasis +export Legendre +export ChebyshevBasis +export Chebyshev +export Bernstein +export BernsteinBasis + +export BernsteinBasisOnSimplex +export bernstein_terms +export bernstein_term_id + +export CompWiseTensorPolyBasis +export NedelecPolyBasisOnSimplex +export RaviartThomasPolyBasis + +export ModalC0Basis +export ModalC0 + +export BarycentricPΛBasis +export BarycentricPmΛBasis +export PΛ_bubbles +export PmΛ_bubbles +export get_bubbles +export print_indices + +export FEEC_space_definition_checks +export FEEC_poly_basis + + +include("PolynomialInterfaces.jl") + +include("CartProdPolyBases.jl") + +include("CompWiseTensorPolyBases.jl") + +include("NedelecPolyBases.jl") + +include("RaviartThomasPolyBases.jl") include("MonomialBases.jl") -include("QGradMonomialBases.jl") +include("LegendreBases.jl") -include("QCurlGradMonomialBases.jl") +include("ChebyshevBases.jl") -include("PCurlGradMonomialBases.jl") +include("BernsteinBases.jl") include("ModalC0Bases.jl") -include("JacobiPolynomialBases.jl") +include("BarycentricPΛBases.jl") + +include("ExteriorCalculusBases.jl") + +include("Deprecated.jl") end # module diff --git a/src/Polynomials/QCurlGradMonomialBases.jl b/src/Polynomials/QCurlGradMonomialBases.jl deleted file mode 100644 index 56acd42e3..000000000 --- a/src/Polynomials/QCurlGradMonomialBases.jl +++ /dev/null @@ -1,79 +0,0 @@ - -""" - struct QCurlGradMonomialBasis{...} <: AbstractArray{Monomial} - -This type implements a multivariate vector-valued polynomial basis -spanning the space needed for Raviart-Thomas reference elements on n-cubes. -The type parameters and fields of this `struct` are not public. -This type fully implements the [`Field`](@ref) interface, with up to first order -derivatives. -""" -struct QCurlGradMonomialBasis{D,T} <: AbstractVector{Monomial} - qgrad::QGradMonomialBasis{D,T} - function QCurlGradMonomialBasis(::Type{T},order::Int,terms::CartesianIndices{D},perms::Matrix{Int}) where {D,T} - qgrad = QGradMonomialBasis(T,order,terms,perms) - new{D,T}(qgrad) - end -end - -Base.size(a::QCurlGradMonomialBasis) = (length(a.qgrad),) -# @santiagobadia : Not sure we want to create the monomial machinery -Base.getindex(a::QCurlGradMonomialBasis,i::Integer) = Monomial() -Base.IndexStyle(::QCurlGradMonomialBasis) = IndexLinear() - -function testvalue(::Type{QCurlGradMonomialBasis{D,T}}) where {D,T} - QCurlGradMonomialBasis{D}(T,0) -end - -""" - QCurlGradMonomialBasis{D}(::Type{T},order::Int) where {D,T} - -Returns a `QCurlGradMonomialBasis` object. `D` is the dimension -of the coordinate space and `T` is the type of the components in the vector-value. -The `order` argument has the following meaning: the divergence of the functions in this basis -is in the Q space of degree `order`. -""" -function QCurlGradMonomialBasis{D}(::Type{T},order::Int) where {D,T} - @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value" - _order = order+1 - _t = tfill(_order,Val{D-1}()) - t = (_order+1,_t...) - terms = CartesianIndices(t) - perms = _prepare_perms(D) - QCurlGradMonomialBasis(T,order,terms,perms) -end - -# @santiagobadia: This is dirty, I would put here VectorValue{D,T} -return_type(::QCurlGradMonomialBasis{D,T}) where {D,T} = VectorValue{D,T} - -function return_cache(f::QCurlGradMonomialBasis,x::AbstractVector{<:Point}) - return_cache(f.qgrad,x) -end - -function evaluate!(cache,f::QCurlGradMonomialBasis,x::AbstractVector{<:Point}) - evaluate!(cache,f.qgrad,x) -end - -function return_cache( - fg::FieldGradientArray{N,<:QCurlGradMonomialBasis}, - x::AbstractVector{<:Point}) where N - - f = fg.fa - return_cache(FieldGradientArray{N}(f.qgrad),x) -end - -function evaluate!( - cache, - fg::FieldGradientArray{N,<:QCurlGradMonomialBasis}, - x::AbstractVector{<:Point}) where N - - f = fg.fa - evaluate!(cache,FieldGradientArray{N}(f.qgrad),x) -end - -""" - num_terms(f::QCurlGradMonomialBasis{D,T}) where {D,T} -""" -num_terms(f::QCurlGradMonomialBasis{D,T}) where {D,T} = length(f.qgrad.terms)*D - -get_order(f::QCurlGradMonomialBasis{D,T}) where {D,T} = get_order(f.qgrad) diff --git a/src/Polynomials/QGradMonomialBases.jl b/src/Polynomials/QGradMonomialBases.jl deleted file mode 100644 index 094fcda0d..000000000 --- a/src/Polynomials/QGradMonomialBases.jl +++ /dev/null @@ -1,489 +0,0 @@ - -""" - struct QGradMonomialBasis{...} <: AbstractVector{Monomial} - -This type implements a multivariate vector-valued polynomial basis -spanning the space needed for Nedelec reference elements on n-cubes. -The type parameters and fields of this `struct` are not public. -This type fully implements the [`Field`](@ref) interface, with up to first order -derivatives. -""" -struct QGradMonomialBasis{D,T} <: AbstractVector{Monomial} - order::Int - terms::CartesianIndices{D} - perms::Matrix{Int} - function QGradMonomialBasis(::Type{T},order::Int,terms::CartesianIndices{D},perms::Matrix{Int}) where {D,T} - new{D,T}(order,terms,perms) - end -end - -Base.size(a::QGradMonomialBasis) = (_ndofs_qgrad(a),) -# @santiagobadia : Not sure we want to create the monomial machinery -Base.getindex(a::QGradMonomialBasis,i::Integer) = Monomial() -Base.IndexStyle(::QGradMonomialBasis) = IndexLinear() -return_type(::QGradMonomialBasis{D,T}) where {D,T} = VectorValue{D,T} - -function testvalue(::Type{QGradMonomialBasis{D,T}}) where {D,T} - QGradMonomialBasis{D}(T,0) -end - -""" - QGradMonomialBasis{D}(::Type{T},order::Int) where {D,T} - -Returns a `QGradMonomialBasis` object. `D` is the dimension -of the coordinate space and `T` is the type of the components in the vector-value. -The `order` argument has the following meaning: the curl of the functions in this basis -is in the Q space of degree `order`. -""" -function QGradMonomialBasis{D}(::Type{T},order::Int) where {D,T} - @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value" - _order = order + 1 - _t = tfill(_order+1,Val{D-1}()) - t = (_order,_t...) - terms = CartesianIndices(t) - perms = _prepare_perms(D) - QGradMonomialBasis(T,order,terms,perms) -end - -""" - num_terms(f::QGradMonomialBasis{D,T}) where {D,T} -""" -num_terms(f::QGradMonomialBasis{D,T}) where {D,T} = length(f.terms)*D - -get_order(f::QGradMonomialBasis) = f.order - -function return_cache(f::QGradMonomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} - @check D == length(eltype(x)) "Incorrect number of point components" - np = length(x) - ndof = _ndofs_qgrad(f) - n = 1 + f.order+1 - V = VectorValue{D,T} - r = CachedArray(zeros(V,(np,ndof))) - v = CachedArray(zeros(V,(ndof,))) - c = CachedArray(zeros(T,(D,n))) - (r, v, c) -end - -function evaluate!(cache,f::QGradMonomialBasis{D,T},x::AbstractVector{<:Point}) where {D,T} - r, v, c = cache - np = length(x) - ndof = _ndofs_qgrad(f) - n = 1 + f.order+1 - setsize!(r,(np,ndof)) - setsize!(v,(ndof,)) - setsize!(c,(D,n)) - for i in 1:np - @inbounds xi = x[i] - _evaluate_nd_qgrad!(v,xi,f.order+1,f.terms,f.perms,c) - for j in 1:ndof - @inbounds r[i,j] = v[j] - end - end - r.array -end - -function return_cache( - fg::FieldGradientArray{1,QGradMonomialBasis{D,T}}, - x::AbstractVector{<:Point}) where {D,T} - - f = fg.fa - @check D == length(eltype(x)) "Incorrect number of point components" - np = length(x) - ndof = _ndofs_qgrad(f) - n = 1 + f.order+1 - xi = testitem(x) - V = VectorValue{D,T} - G = gradient_type(V,xi) - r = CachedArray(zeros(G,(np,ndof))) - v = CachedArray(zeros(G,(ndof,))) - c = CachedArray(zeros(T,(D,n))) - g = CachedArray(zeros(T,(D,n))) - (r, v, c, g) -end - -function evaluate!( - cache, - fg::FieldGradientArray{1,QGradMonomialBasis{D,T}}, - x::AbstractVector{<:Point}) where {D,T} - - f = fg.fa - r, v, c, g = cache - np = length(x) - ndof = _ndofs_qgrad(f) - n = 1 + f.order+1 - setsize!(r,(np,ndof)) - setsize!(v,(ndof,)) - setsize!(c,(D,n)) - setsize!(g,(D,n)) - V = VectorValue{D,T} - for i in 1:np - @inbounds xi = x[i] - _gradient_nd_qgrad!(v,xi,f.order+1,f.terms,f.perms,c,g,V) - for j in 1:ndof - @inbounds r[i,j] = v[j] - end - end - r.array -end - -# Helpers - -_ndofs_qgrad(f::QGradMonomialBasis{D}) where D = D*(length(f.terms)) - -function _prepare_perms(D) - perms = zeros(Int,D,D) - for j in 1:D - for d in j:D - perms[d,j] = d-j+1 - end - for d in 1:(j-1) - perms[d,j] = d+(D-j)+1 - end - end - perms -end - -function _evaluate_nd_qgrad!( - v::AbstractVector{V}, - x, - order, - terms::CartesianIndices{D}, - perms::Matrix{Int}, - c::AbstractMatrix{T}) where {V,T,D} - - dim = D - for d in 1:dim - _evaluate_1d!(c,x,order,d) - end - - o = one(T) - k = 1 - m = zero(Mutable(V)) - js = eachindex(m) - z = zero(T) - - for ci in terms - - for j in js - - @inbounds for i in js - m[i] = z - end - - s = o - @inbounds for d in 1:dim - s *= c[d,ci[perms[d,j]]] - end - - m[j] = s - v[k] = m - k += 1 - - end - - end - -end - -function _gradient_nd_qgrad!( - v::AbstractVector{G}, - x, - order, - terms::CartesianIndices{D}, - perms::Matrix{Int}, - c::AbstractMatrix{T}, - g::AbstractMatrix{T}, - ::Type{V}) where {G,T,D,V} - - dim = D - for d in 1:dim - _evaluate_1d!(c,x,order,d) - _gradient_1d!(g,x,order,d) - end - - z = zero(Mutable(V)) - m = zero(Mutable(G)) - js = eachindex(z) - mjs = eachindex(m) - o = one(T) - zi = zero(T) - k = 1 - - for ci in terms - - for j in js - - s = z - for i in js - s[i] = o - end - - for q in 1:dim - for d in 1:dim - if d != q - @inbounds s[q] *= c[d,ci[perms[d,j]]] - else - @inbounds s[q] *= g[d,ci[perms[d,j]]] - end - end - end - - @inbounds for i in mjs - m[i] = zi - end - - for i in js - @inbounds m[i,j] = s[i] - end - @inbounds v[k] = m - k += 1 - - end - - end - -end - -struct NedelecPrebasisOnSimplex{D} <: AbstractVector{Monomial} - order::Int - function NedelecPrebasisOnSimplex{D}(order::Integer) where D - new{D}(Int(order)) - end -end - -function Base.size(a::NedelecPrebasisOnSimplex{d}) where d - k = a.order+1 - n = div(k*prod(i->(k+i),2:d),factorial(d-1)) - (n,) -end - -Base.getindex(a::NedelecPrebasisOnSimplex,i::Integer) = Monomial() -Base.IndexStyle(::Type{<:NedelecPrebasisOnSimplex}) = IndexLinear() - -return_type(::NedelecPrebasisOnSimplex{D}) where {D} = VectorValue{D,Float64} -num_terms(a::NedelecPrebasisOnSimplex) = length(a) -get_order(f::NedelecPrebasisOnSimplex) = f.order - -function return_cache( - f::NedelecPrebasisOnSimplex{d},x::AbstractVector{<:Point}) where d - np = length(x) - ndofs = num_terms(f) - V = eltype(x) - a = zeros(V,(np,ndofs)) - k = f.order+1 - P = MonomialBasis{d}(VectorValue{d,Float64},k-1,(e,order)->sum(e)<=order) - cP = return_cache(P,x) - CachedArray(a), cP, P -end - -function evaluate!( - cache,f::NedelecPrebasisOnSimplex{3},x::AbstractVector{<:Point}) - ca,cP,P = cache - k = f.order+1 - np = length(x) - ndofs = num_terms(f) - ndofsP = length(P) - setsize!(ca,(np,ndofs)) - Px = evaluate!(cP,P,x) - a = ca.array - V = eltype(x) - T = eltype(V) - z = zero(T) - u = one(T) - for (ip,p) in enumerate(x) - for j in 1:ndofsP - a[ip,j] = Px[ip,j] - end - i = ndofsP - x1,x2,x3 = x[ip] - zp = zero(x1) - for β in 1:k - for α in 1:(k+1-β) - i += 1 - a[ip,i] = VectorValue( - -x1^(α-1)*x2^(k-α-β+2)*x3^(β-1), - x1^α*x2^(k-α-β+1)*x3^(β-1), - zp) - i += 1 - a[ip,i] = VectorValue( - -x1^(k-α-β+1)*x2^(β-1)*x3^α, - zp, - x1^(k-α-β+2)*x2^(β-1)*x3^(α-1)) - end - end - for γ in 1:k - i += 1 - a[ip,i] = VectorValue( - zp, - -x2^(γ-1)*x3^(k-γ+1), - x2^γ*x3^(k-γ)) - end - end - a -end - -function evaluate!( - cache,f::NedelecPrebasisOnSimplex{2},x::AbstractVector{<:Point}) - ca,cP,P = cache - k = f.order+1 - np = length(x) - ndofs = num_terms(f) - ndofsP = length(P) - setsize!(ca,(np,ndofs)) - a = ca.array - V = eltype(x) - T = eltype(V) - z = zero(T) - u = one(T) - Px = evaluate!(cP,P,x) - for (ip,p) in enumerate(x) - for j in 1:ndofsP - a[ip,j] = Px[ip,j] - end - i = ndofsP - x1,x2 = x[ip] - zp = zero(x1) - for α in 1:k - i += 1 - a[ip,i] = VectorValue(-x1^(α-1)*x2^(k-α+1),x1^α*x2^(k-α)) - end - #a[ip,1] = VectorValue((u,z)) - #a[ip,2] = VectorValue((z,u)) - #a[ip,3] = VectorValue((-p[2],p[1])) - end - a -end - -function return_cache( - g::FieldGradientArray{1,<:NedelecPrebasisOnSimplex{D}}, - x::AbstractVector{<:Point}) where D - f = g.fa - np = length(x) - ndofs = num_terms(f) - xi = testitem(x) - V = eltype(x) - G = gradient_type(V,xi) - a = zeros(G,(np,ndofs)) - k = f.order+1 - mb = MonomialBasis{D}(VectorValue{D,Float64},k-1,(e,order)->sum(e)<=order) - P = Broadcasting(∇)(mb) - cP = return_cache(P,x) - CachedArray(a), cP, P -end - -function evaluate!( - cache, - g::FieldGradientArray{1,<:NedelecPrebasisOnSimplex{3}}, - x::AbstractVector{<:Point}) - ca,cP,P = cache - f = g.fa - k = f.order+1 - np = length(x) - ndofs = num_terms(f) - setsize!(ca,(np,ndofs)) - a = ca.array - fill!(a,zero(eltype(a))) - ndofsP = length(P) - Px = evaluate!(cP,P,x) - V = eltype(x) - T = eltype(V) - z = zero(T) - u = one(T) - for (ip,p) in enumerate(x) - for j in 1:ndofsP - a[ip,j] = Px[ip,j] - end - i = ndofsP - x1,x2,x3 = x[ip] - zp = zero(x1) - for β in 1:k - for α in 1:(k+1-β) - i += 1 - a[ip,i] = TensorValue( - #-x1^(α-1)*x2^(k-α-β+2)*x3^(β-1), - -(α-1)*_exp(x1,α-2)*x2^(k-α-β+2)*x3^(β-1), - -x1^(α-1)*(k-α-β+2)*_exp(x2,k-α-β+1)*x3^(β-1), - -x1^(α-1)*x2^(k-α-β+2)*(β-1)*_exp(x3,β-2), - #x1^α*x2^(k-α-β+1)*x3^(β-1), - α*_exp(x1,α-1)*x2^(k-α-β+1)*x3^(β-1), - x1^α*(k-α-β+1)*_exp(x2,k-α-β)*x3^(β-1), - x1^α*x2^(k-α-β+1)*(β-1)*_exp(x3,β-2), - #zp, - zp,zp,zp) - i += 1 - a[ip,i] = TensorValue( - #-x1^(k-α-β+1)*x2^(β-1)*x3^α, - -(k-α-β+1)*_exp(x1,k-α-β)*x2^(β-1)*x3^α, - -x1^(k-α-β+1)*(β-1)*_exp(x2,β-2)*x3^α, - -x1^(k-α-β+1)*x2^(β-1)*α*_exp(x3,α-1), - # zp - zp,zp,zp, - #x1^(k-α-β+2)*x2^(β-1)*x3^(α-1), - (k-α-β+2)*_exp(x1,k-α-β+1)*x2^(β-1)*x3^(α-1), - x1^(k-α-β+2)*(β-1)*_exp(x2,β-2)*x3^(α-1), - x1^(k-α-β+2)*x2^(β-1)*(α-1)*_exp(x3,α-2)) - end - end - for γ in 1:k - i += 1 - a[ip,i] = TensorValue( - #zp - zp,zp,zp, - #-x2^(γ-1)*x3^(k-γ+1), - -0*x2^(γ-1)*x3^(k-γ+1), - -(γ-1)*_exp(x2,γ-2)*x3^(k-γ+1), - -x2^(γ-1)*(k-γ+1)*_exp(x3,k-γ), - #x2^γ*x3^(k-γ), - 0*x2^γ*x3^(k-γ), - γ*_exp(x2,γ-1)*x3^(k-γ), - x2^γ*(k-γ)*_exp(x3,k-γ-1)) - end - #a[ip,4] = TensorValue((z,-u,z, u,z,z, z,z,z)) - #a[ip,5] = TensorValue((z,z,-u, z,z,z, u,z,z)) - #a[ip,6] = TensorValue((z,z,z, z,z,-u, z,u,z)) - end - a -end - -_exp(a,y) = y>0 ? a^y : one(a) - -function evaluate!( - cache, - g::FieldGradientArray{1,<:NedelecPrebasisOnSimplex{2}}, - x::AbstractVector{<:Point}) - f = g.fa - ca,cP,P = cache - k = f.order+1 - np = length(x) - ndofs = num_terms(f) - setsize!(ca,(np,ndofs)) - a = ca.array - fill!(a,zero(eltype(a))) - V = eltype(x) - T = eltype(V) - z = zero(T) - u = one(T) - ndofsP = length(P) - Px = evaluate!(cP,P,x) - for (ip,p) in enumerate(x) - for j in 1:ndofsP - a[ip,j] = Px[ip,j] - end - i = ndofsP - x1,x2 = x[ip] - zp = zero(x1) - for α in 1:k - i += 1 - a[ip,i] = TensorValue( - #-x1^(α-1)*x2^(k-α+1), - -(α-1)*_exp(x1,α-2)*x2^(k-α+1), - -x1^(α-1)*(k-α+1)*_exp(x2,k-α), - #x1^α*x2^(k-α), - α*_exp(x1,α-1)*x2^(k-α), - x1^α*(k-α)*_exp(x2,k-α-1)) - end - #a[ip,3] = TensorValue((z,-u, u,z)) - end - a -end - diff --git a/src/Polynomials/RaviartThomasPolyBases.jl b/src/Polynomials/RaviartThomasPolyBases.jl new file mode 100644 index 000000000..c402ee2ee --- /dev/null +++ b/src/Polynomials/RaviartThomasPolyBases.jl @@ -0,0 +1,203 @@ +""" + RaviartThomasPolyBasis{D,V,PT} <: PolynomialBasis{D,V,PT} + +Basis of the vector valued (`V<:VectorValue{D}`) space + +ℝ𝕋ᴰₙ = (𝕊ₙ)ᴰ ⊕ x (𝕊ₙ\\𝕊₍ₙ₋₁₎) + +where 𝕊ₙ is a `D`-multivariate scalar polynomial space of maximum degree n = `K`-1. + +This ℝ𝕋ᴰₙ is the polynomial space for Raviart-Thomas elements with divergence in 𝕊ₙ. +Its maximum degree, that `get_order` returns, is n+1 = `K`. + +!!! warning + Using this basis on simplices is not recommanded, [`BarycentricPmΛBasis`](@ref) is better numerically conditioned for higher degrees, they are obtained by using `Bernstein` as argument of [`FEEC_poly_basis`](@ref) . + +# Example: + +```@example +# a basis for Raviart-Thomas on tetrahedra with divergence in ℙ₂ +b = RaviartThomasPolyBasis{3}(Monomial, Float64, 2) + +# a basis for Raviart-Thomas on quadrilateral with divergence in ℙ₃ +b = RaviartThomasPolyBasis{2}(Monomial, Float64, 3, _q_filter) +``` + +The space 𝕊ₙ, typically ℙᴰₙ or ℚᴰₙ, does not need to have a tensor product +structure of 1D scalar spaces. Thus, the ℝ𝕋ᴰₙ component's scalar spaces are not +tensor products either. + +𝕊ₙ is defined like a scalar valued [`CartProdPolyBasis`](@ref) via the `_filter` +argument of the constructor, by default `_p_filter` for ℙᴰₙ. +As a consequence, `PT` must be hierarchical, see [`isHierarchical`](@ref). +""" +struct RaviartThomasPolyBasis{D,V,PT} <: PolynomialBasis{D,V,PT} + max_order::Int + pterms::Vector{CartesianIndex{D}} + sterms::Vector{CartesianIndex{D}} + + @doc""" + RaviartThomasPolyBasis{D}(::Type{PT}, ::Type{T}, order::Int, _filter::Function=_p_filter) + + Where `_filter` defines 𝕊ₙ and `order` = n = K-1 (cf. struct docstring). + """ + function RaviartThomasPolyBasis{D}( + ::Type{PT}, ::Type{T}, order::Int, + _filter::Function=_p_filter + ) where {PT<:Polynomial,D,T} + + @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value" + @check D > 1 + @check isconcretetype(PT) "PT needs to be a concrete <:Polynomial type" + @check isHierarchical(PT) "The polynomial basis must be hierarchical for this space." + + V = VectorValue{D,T} + indexbase = 1 + + # terms defining 𝕊ₙ + P_k = MonomialBasis(Val(D), T, order, _filter) + pterms = P_k.terms + msg = "Some term defining `𝕊ₙ` contain a higher index than the maximum, + `order`+1, please fix the `_filter` argument" + @check all( pterm -> (maximum(Tuple(pterm) .- indexbase, init=0) <= order), pterms) msg + + # terms defining 𝕊ₙ\𝕊ₙ₋₁ + _minus_one_order_filter = term -> _filter(Tuple(term) .- indexbase, order-1) + sterms = filter(!_minus_one_order_filter, pterms) + + new{D,V,PT}(order+1,pterms,sterms) + end +end + +Base.size(b::RaviartThomasPolyBasis{D}) where {D} = (D*length(b.pterms) + length(b.sterms), ) +get_order(b::RaviartThomasPolyBasis) = b.max_order +get_orders(b::RaviartThomasPolyBasis{D}) where D = tfill(get_order(b), Val(D)) + +function testvalue(::Type{RaviartThomasPolyBasis{D,V,PT}}) where {D,V,PT} + T = eltype(V) + RaviartThomasPolyBasis{D}(PT, T, 0) +end + +################################# +# nD evaluations implementation # +################################# + +function _evaluate_nd!( + b::RaviartThomasPolyBasis{D,V,PT}, x, + r::AbstractMatrix{Vr}, i, + c::AbstractMatrix{T}, ::Val{K}) where {D,V,PT,Vr,T,K} + + for d in 1:D + _evaluate_1d!(PT,K,c,x,d) + end + + m = zero(Mutable(Vr)) + k = 1 + + @inbounds begin + for l in 1:D + for ci in b.pterms + + s = one(T) + for d in 1:D + s *= c[d,ci[d]] + end + + k = _comp_wize_set_value!(r,i,s,k,l) + end + end + + for ci in b.sterms + for i in 1:D + m[i] = zero(T) + end + + for l in 1:D + + s = x[l] + for d in 1:D + s *= c[d,ci[d]] + end + + m[l] = s + end + + r[i,k] = m + k += 1 + end + + end +end + +function _gradient_nd!( + b::RaviartThomasPolyBasis{D,V,PT}, x, + r::AbstractMatrix{G}, i, + c::AbstractMatrix{T}, + g::AbstractMatrix{T}, + s::MVector{D,T}, ::Val{K}) where {D,V,PT,G,T,K} + + for d in 1:D + _derivatives_1d!(PT,K,(c,g),x,d) + end + + m = zero(Mutable(G)) + k = 1 + + @inbounds begin + for l in 1:D + for ci in b.pterms + + for i in eachindex(s) + s[i] = one(T) + end + + for q in 1:D + for d in 1:D + if d != q + s[q] *= c[d,ci[d]] + else + s[q] *= g[d,ci[d]] + end + end + end + + k = _comp_wize_set_derivative!(r,i,s,k,l,V) + end + end + + for ci in b.sterms + + for i in eachindex(m) + m[i] = zero(T) + end + + for l in 1:D + + for i in eachindex(s) + s[i] = x[l] + end + + aux = one(T) + for q in 1:D + aux *= c[q,ci[q]] + for d in 1:D + if d != q + s[q] *= c[d,ci[d]] + else + s[q] *= g[d,ci[d]] + end + end + end + s[l] += aux + + for i in 1:D + m[i,l] = s[i] + end + end + r[i,k] = m + k += 1 + end + + end +end + diff --git a/src/ReferenceFEs/AWRefFEs.jl b/src/ReferenceFEs/AWRefFEs.jl new file mode 100644 index 000000000..c79dfa359 --- /dev/null +++ b/src/ReferenceFEs/AWRefFEs.jl @@ -0,0 +1,83 @@ + +struct ArnoldWinther <: ReferenceFEName end + +const arnold_winther = ArnoldWinther() + +Pushforward(::Type{ArnoldWinther}) = DoubleContraVariantPiolaMap() + +""" + struct ArnoldWinther <: ReferenceFEName end + ArnoldWintherRefFE(::Type{T},p::Polytope,order::Integer) where T + +Arnold-Winther reference finite element. + +References: + +- `Mixed Finite Elements for Elasticity`, Arnold and Winther (2002) +- `Nonconforming Mixed Finite Elements for Elasticity`, Arnold and Winther (2003) +- `Transformations for Piola-mapped elements`, Aznaran, Farrell and Kirby (2022) + +""" +function ArnoldWintherRefFE(::Type{T},p::Polytope,order::Integer) where T + @assert p == TRI "ArnoldWinther Reference FE only defined for TRIangles" + @assert order == 2 "ArnoldWinther Reference FE only defined for order 2" + conforming = true # TODO: Make this an argument + + VT = SymTensorValue{2,T} + prebasis = MonomialBasis(Val(2),VT,3,Polynomials._p_filter) + fb = MonomialBasis(Val(1),T,0,Polynomials._p_filter) + cb = map(constant_field,component_basis(VT)) + + function cmom(φ,μ,ds) # Cell and Node moment function: σ_K(φ,μ) = ∫(φ:μ)dK + Broadcasting(Operation(⊙))(φ,μ) + end + function fmom_n(φ,μ,ds) # Face moment function (normal) : σ_F(φ,μ) = ∫((n·φ·n)*μ)dF + n = get_facet_normal(ds) + φn = Broadcasting(Operation(⋅))(φ,n) + nφn = Broadcasting(Operation(⋅))(n,φn) + Broadcasting(Operation(*))(nφn,μ) + end + function fmom_t(φ,μ,ds) # Face moment function (tangent) : σ_F(φ,μ) = ∫((n·φ·t)*μ)dF + n = get_facet_normal(ds) + t = get_edge_tangent(ds) + φn = Broadcasting(Operation(⋅))(φ,t) + nφn = Broadcasting(Operation(⋅))(n,φn) + Broadcasting(Operation(*))(nφn,μ) + end + + moments = Tuple[ + (get_dimrange(p,1),fmom_n,fb), # Face moments (normal-normal) + (get_dimrange(p,1),fmom_t,fb), # Face moments (normal-tangent) + (get_dimrange(p,2),cmom,cb) # Cell moments + ] + + if conforming + node_moments = Tuple[(get_dimrange(p,0),cmom,cb)] # Node moments + moments = vcat(node_moments,moments) + end + + return MomentBasedReferenceFE(arnold_winther,p,prebasis,moments,DivConformity()) +end + +function ReferenceFE(p::Polytope,::ArnoldWinther,::Type{T}, order) where T + ArnoldWintherRefFE(T,p,order) +end + +function Conformity(reffe::GenericRefFE{ArnoldWinther},sym::Symbol) + hdiv = (:Hdiv,:HDiv) + if sym == :L2 + L2Conformity() + elseif sym in hdiv + DivConformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a ArnoldWinther reference FE. + + Possible values of conformity for this reference fe are $((:L2, hdiv...)). + """ + end +end + +function get_face_own_dofs(reffe::GenericRefFE{ArnoldWinther}, conf::DivConformity) + get_face_dofs(reffe) +end diff --git a/src/ReferenceFEs/BDMRefFEs.jl b/src/ReferenceFEs/BDMRefFEs.jl index 81d3bd9af..1354a8755 100644 --- a/src/ReferenceFEs/BDMRefFEs.jl +++ b/src/ReferenceFEs/BDMRefFEs.jl @@ -1,56 +1,66 @@ -struct BDM <: DivConforming end - -const bdm = BDM() - """ -BDMRefFE(::Type{et},p::Polytope,order::Integer) where et - -The `order` argument has the following meaning: the divergence of the functions in this basis -is in the P space of degree `order-1`. - + struct BDM <: ReferenceFEName """ -function BDMRefFE(::Type{et},p::Polytope,order::Integer) where et +struct BDM <: ReferenceFEName end - D = num_dims(p) +""" + const bdm = BDM() - vet = VectorValue{num_dims(p),et} +Singleton of the [`BDM`](@ref) reference FE name. +""" +const bdm = BDM() - if is_simplex(p) - prebasis = MonomialBasis(vet,p,order) - else - @notimplemented "BDM Reference FE only available for simplices" - end +Pushforward(::Type{BDM}) = ContraVariantPiolaMap() - nf_nodes, nf_moments = _BDM_nodes_and_moments(et,p,order,GenericField(identity)) +""" + BDMRefFE(::Type{T}, p::Polytope, order::Integer; kwargs...) - face_own_dofs = _face_own_dofs_from_moments(nf_moments) +The `order` argument has the following meaning: the divergence of the functions +in this basis is in the ℙ space of degree `order`. `T` is the type of scalar +components. - face_dofs = face_own_dofs +The `kwargs` are [`change_dof`](@ref "`change_dof` keyword argument"), +[`poly_type`](@ref "`poly_type` keyword argument") and +[`mom_poly_type`](@ref "`mom_poly_type` keyword argument"). +""" +function BDMRefFE( + ::Type{T},p::Polytope{D},order::Integer; + change_dof=true, poly_type=_mom_reffe_default_PT(p), mom_poly_type=poly_type) where {T,D} - dof_basis = MomentBasedDofBasis(nf_nodes, nf_moments) + @check order > 0 "BDM Reference FE only available for order > 0, got order=$order" + @check 2 ≤ D ≤ 3 && is_simplex(p) "BDM Reference FE only available for simplices of dimension 2 and 3" - ndofs = num_dofs(dof_basis) + PT, MPT = poly_type, mom_poly_type + rotate_90 = (D==2) + k = D-1 - metadata = nothing + prebasis = FEEC_poly_basis(Val(D), T,order ,k,:P, PT; rotate_90) # PᵣΛᴰ⁻¹, r = order + fb = FEEC_poly_basis(Val(D-1),T,order ,0,:P⁻,MPT) # Facet basis P⁻ᵨΛ⁰(△ᴰ⁻¹), ρ = r + cb = order>1 ? FEEC_poly_basis(Val(D), T,order-1,1,:P⁻,MPT) : nothing # Cell basis P⁻ᵨΛ¹(△ᴰ), ρ = r-1 - reffe = GenericRefFE{BDM}( - ndofs, - p, - prebasis, - dof_basis, - DivConformity(), - metadata, - face_dofs) + function cmom(φ,μ,ds) # Cell moment function: σ_K(φ,μ) = ∫(φ·μ)dK + Broadcasting(Operation(⋅))(φ,μ) + end + function fmom(φ,μ,ds) # Face moment function : σ_F(φ,μ) = ∫((φ·n)*μ)dF + n = get_facet_normal(ds) + φn = Broadcasting(Operation(⋅))(φ,n) + Broadcasting(Operation(*))(φn,μ) + end - reffe -end + moments = Tuple[ + (get_dimrange(p,D-1),fmom,fb), # Face moments + ] + if order > 1 + push!(moments,(get_dimrange(p,D),cmom,cb)) # Cell moments + end -function ReferenceFE(p::Polytope,::BDM, order) - BDMRefFE(Float64,p,order) + conf = DivConformity() + change_dof = _validate_change_dof(change_dof, prebasis, p, conf) + return MomentBasedReferenceFE(bdm,p,prebasis,moments,conf; change_dof) end -function ReferenceFE(p::Polytope,::BDM,::Type{T}, order) where T - BDMRefFE(T,p,order) +function ReferenceFE(p::Polytope,::BDM,::Type{T}, order; kwargs...) where T + BDMRefFE(T,p,order; kwargs...) end function Conformity(reffe::GenericRefFE{BDM},sym::Symbol) @@ -65,128 +75,9 @@ function Conformity(reffe::GenericRefFE{BDM},sym::Symbol) Possible values of conformity for this reference fe are $((:L2, hdiv...)). """ - end - end - - function get_face_own_dofs(reffe::GenericRefFE{BDM}, conf::DivConformity) - get_face_dofs(reffe) end +end - function _BDM_nodes_and_moments(::Type{et}, p::Polytope, order::Integer, phi::Field) where et - - D = num_dims(p) - ft = VectorValue{D,et} - pt = Point{D,et} - - nf_nodes = [ zeros(pt,0) for face in 1:num_faces(p)] - nf_moments = [ zeros(ft,0,0) for face in 1:num_faces(p)] - - fcips, fmoments = _BDM_face_values(p,et,order,phi) - frange = get_dimrange(p,D-1) - nf_nodes[frange] = fcips - nf_moments[frange] = fmoments - - if (order > 1) - ccips, cmoments = _BDM_cell_values(p,et,order,phi) - crange = get_dimrange(p,D) - nf_nodes[crange] = ccips - nf_moments[crange] = cmoments - end - - nf_nodes, nf_moments - end - - function _BDM_face_moments(p, fshfs, c_fips, fcips, fwips,phi) - nc = length(c_fips) - cfshfs = fill(fshfs, nc) - cvals = lazy_map(evaluate,cfshfs,c_fips) - cvals = [fwips[i].*cvals[i] for i in 1:nc] - fns = get_facet_normal(p) - - # Must express the normal in terms of the real/reference system of - # coordinates (depending if phi≡I or phi is a mapping, resp.) - # Hence, J = transpose(grad(phi)) - - Jt = fill(∇(phi),nc) - Jt_inv = lazy_map(Operation(pinvJt),Jt) - det_Jt = lazy_map(Operation(meas),Jt) - change = lazy_map(*,det_Jt,Jt_inv) - change_ips = lazy_map(evaluate,change,fcips) - - cvals = [ _broadcast(typeof(n),n,J.*b) for (n,b,J) in zip(fns,cvals,change_ips)] - - return cvals - end - - # It provides for every face the nodes and the moments arrays - function _BDM_face_values(p,et,order,phi) - - # Reference facet - @check is_simplex(p) "We are assuming that all n-faces of the same n-dim are the same." - fp = Polytope{num_dims(p)-1}(p,1) - - # geomap from ref face to polytope faces - fgeomap = _ref_face_to_faces_geomap(p,fp) - - # Nodes are integration points (for exact integration) - # Thus, we define the integration points in the reference - # face polytope (fips and wips). Next, we consider the - # n-face-wise arrays of nodes in fp (constant cell array c_fips) - # the one of the points in the polytope after applying the geopmap - # (fcips), and the weights for these nodes (fwips, a constant cell array) - # Nodes (fcips) - degree = (order)*2 - fquad = Quadrature(fp,degree) - fips = get_coordinates(fquad) - wips = get_weights(fquad) - - c_fips, fcips, fwips = _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) - - # Moments (fmoments) - # The BDM prebasis is expressed in terms of shape function - fshfs = MonomialBasis(et,fp,order) - - # Face moments, i.e., M(Fi)_{ab} = q_RF^a(xgp_RFi^b) w_Fi^b n_Fi ⋅ () - fmoments = _BDM_face_moments(p, fshfs, c_fips, fcips, fwips, phi) - - return fcips, fmoments - - end - - function _BDM_cell_moments(p, cbasis, ccips, cwips) - # Interior DOFs-related basis evaluated at interior integration points - ishfs_iips = evaluate(cbasis,ccips) - return cwips.⋅ishfs_iips - end - - # It provides for every cell the nodes and the moments arrays - function _BDM_cell_values(p,et,order,phi) - # Compute integration points at interior - degree = 2*(order) - iquad = Quadrature(p,degree) - ccips = get_coordinates(iquad) - cwips = get_weights(iquad) - - # Cell moments, i.e., M(C)_{ab} = q_C^a(xgp_C^b) w_C^b ⋅ () - if is_simplex(p) - T = VectorValue{num_dims(p),et} - # cbasis = GradMonomialBasis{num_dims(p)}(T,order-1) - cbasis = Polynomials.NedelecPrebasisOnSimplex{num_dims(p)}(order-2) - else - @notimplemented - end - cell_moments = _BDM_cell_moments(p, cbasis, ccips, cwips ) - - # Must scale weights using phi map to get the correct integrals - # scaling = meas(grad(phi)) - Jt = ∇(phi) - Jt_inv = pinvJt(Jt) - det_Jt = meas(Jt) - change = det_Jt*Jt_inv - change_ips = evaluate(change,ccips) - - cmoments = change_ips.⋅cell_moments - - return [ccips], [cmoments] - - end +function get_face_own_dofs(reffe::GenericRefFE{BDM}, conf::DivConformity) + get_face_dofs(reffe) +end diff --git a/src/ReferenceFEs/BezierRefFEs.jl b/src/ReferenceFEs/BezierRefFEs.jl index 4620d5620..a7eb0f456 100644 --- a/src/ReferenceFEs/BezierRefFEs.jl +++ b/src/ReferenceFEs/BezierRefFEs.jl @@ -1,7 +1,20 @@ +""" + struct Bezier <: ReferenceFEName +""" struct Bezier <: ReferenceFEName end +""" + const bezier = Bezier() + +Singleton of the [`Bezier`](@ref) reference FE name. +""" const bezier = Bezier() +""" + struct BezierRefFE{D} <: LagrangianRefFE{D} + +The dof basis does not interpolate into the shape functions basis +""" struct BezierRefFE{D} <: LagrangianRefFE{D} reffe::ReferenceFE{D} node_to_own_node::Vector{Int} @@ -9,7 +22,14 @@ struct BezierRefFE{D} <: LagrangianRefFE{D} metadata end +""" + BezierRefFE(::Type{T}, p::Polytope{D}, orders) + +`T` must be scalar. +""" function BezierRefFE(::Type{T},p::Polytope{D},orders) where {D,T} + @notimplementedif T isa MultiValue "Only scalar valued BezierRefFE are implemented, got T=$T." + reffe = LagrangianRefFE(T,p,orders) nodes = get_node_coordinates(reffe) prebasis = get_prebasis(reffe) @@ -94,7 +114,7 @@ end function compute_node_to_bezier_node(prebasis::MonomialBasis{D,T},nodes) where {D,T} orders = get_orders(prebasis) terms = _coords_to_terms(nodes,orders) - _prebasis = MonomialBasis{D}(T,orders,terms) + _prebasis = MonomialBasis(Val(D),T,orders,terms) _exps = get_exponents(_prebasis) exps = get_exponents(prebasis) [ findfirst( isequal(i), exps) for i in _exps ] @@ -162,8 +182,10 @@ function _bernstein_term( args = (order,a...,i...) if is_simplex(p) _bernstein_term(args...) - else + elseif is_n_cube(p) _bernstein_term_n_cube(args...) + else + @unreachable "Bezier reference FEs only defined on simplices and n-cubes, got $p." end end diff --git a/src/ReferenceFEs/BubbleRefFEs.jl b/src/ReferenceFEs/BubbleRefFEs.jl index 794cb7a37..a0a5047ca 100644 --- a/src/ReferenceFEs/BubbleRefFEs.jl +++ b/src/ReferenceFEs/BubbleRefFEs.jl @@ -1,86 +1,119 @@ +""" + struct Bubble <: ReferenceFEName +""" struct Bubble <: ReferenceFEName end + +""" + const bubble = Bubble() + +Singleton of the [`Bubble`](@ref) reference FE name. +""" const bubble = Bubble() +""" + BubbleRefFE(::Type{T}, p::Polytope{D}; type = :mini, coeffs = nothing, terms = nothing) + +A Lagrangian bubble space used to enrich another element. +It contains `num_independent_components(T)` shape functions. + +By default -- `type == :mini` -- this is the bubble for (ℙ1ᴰb–ℙ1) MINI element (with shape +functions of degree 3), it is available for simplices and n-cubes. + +If `coeffs` and `terms` are given, the bubble shape functions are defined by the +[`linear_combination`](@ref) of the weight matrix `coeffs` with the +[`MonomialBasis`](@ref) defined by `terms`. +""" function BubbleRefFE(::Type{T}, p::Polytope{D}; type = :mini, coeffs = nothing, terms = nothing) where {T, D} - @notimplementedif D < 1 "Bubble reference finite elements are only defined for D >= 1." - - if isnothing(coeffs) && isnothing(terms) - if type == :mini - terms, coeffs = _mini_bubble_terms_and_coeffs(T, p) - else - @notimplemented "Only :mini is supported now; however, you may also specify `terms` and `coeffs` manually." - end - elseif isnothing(terms) || isnothing(coeffs) - @error "You must specify both `terms` and `coeffs` or neither." - end - - orders = Tuple(maximum(terms) - oneunit(eltype(terms))) - prebasis = linear_combination(coeffs, MonomialBasis{D}(T, orders, terms)) - x0 = mean(get_vertex_coordinates(p)) - dofs = LagrangianDofBasis(T, [x0]) - ndofs = length(dofs) - face_dofs = [Int[] for _ in 1:num_faces(p)] - face_dofs[end] = 1:ndofs - shapefuncs = compute_shapefuns(dofs, prebasis) - conformity = L2Conformity() - metadata = nothing - - return GenericRefFE{Bubble}( - ndofs, - p, - prebasis, - dofs, - conformity, - metadata, - face_dofs, - shapefuncs, - ) -end + @notimplementedif D < 1 "Bubble reference finite elements are only defined for `D >= 1`." + + if isnothing(coeffs) && isnothing(terms) + if type == :mini + @notimplementedif !is_simplex(p) && !is_n_cube(p) "`:mini` element only implemented for simplices and n-cubes." + terms, coeffs = _mini_bubble_terms_and_coeffs(T, p) + else + @notimplemented "Only `:mini` type is supported now; however, you may also specify `terms` and `coeffs` manually." + end + elseif isnothing(terms) || isnothing(coeffs) + @unreachable "You must specify both `terms` and `coeffs` or neither." + end -get_order(reffe::GenericRefFE{Bubble}) = num_vertices(reffe.polytope) + orders = Tuple(maximum(terms) - oneunit(eltype(terms))) + prebasis = linear_combination(coeffs, MonomialBasis(Val(D), T, orders, terms)) + x0 = mean(get_vertex_coordinates(p)) + dofs = LagrangianDofBasis(T, [x0]) + ndofs = length(dofs) -function ReferenceFE(p::Polytope, ::Bubble, ::Type{T}; kwargs...) where {T} - return BubbleRefFE(T, p; kwargs...) + msg = "Wrong `terms` and/or `coeffs`, the current implementation assumes that the bubble space contains `num_indep_component(T)` shapefunctions, T=$T" + @notimplementedif length(prebasis) != ndofs msg + + face_dofs = [Int[] for _ in 1:num_faces(p)] + face_dofs[end] = 1:ndofs + shapefuncs = compute_shapefuns(dofs, prebasis) + conformity = L2Conformity() + metadata = nothing + + return GenericRefFE{Bubble}( + ndofs, + p, + prebasis, + dofs, + conformity, + metadata, + face_dofs, + shapefuncs, + ) end -function _mini_bubble_terms_and_coeffs(::Type{T}, p::Polytope{D}) where {T, D} - et = eltype(T) - if is_simplex(p) - # x_1x_2...x_D(1-x_1-x_2-...-x_D) - terms = Vector{CartesianIndex{D}}(undef, D+1) - @inbounds for j ∈ 1:D - # x_1x_2...x_{j-1}(x_j^2)x_{j+1}...x_D - terms[j] = CartesianIndex(ntuple(i->i==j ? 3 : 2, Val{D}())) - end - # x_1x_2...x_D - terms[D+1] = CartesianIndex(tfill(2, Val{D}())) - coeff = fill(-one(et), D+1) - coeff[D+1] = one(et) - elseif is_n_cube(p) - # x_1x_2...x_D(1-x_1)(1-x_2)...(1-x_D) - terms = Vector{CartesianIndex{D}}(undef, 2^D) - coeff = Vector{et}(undef, 2^D) - offset = 1 - # Loop through all terms in the binomial expansion. - @inbounds for n ∈ 0:D - sign = (-one(et))^n - for idx ∈ combinations(1:D, n) - terms[offset] = CartesianIndex(ntuple(i -> i in idx ? 3 : 2, Val{D}())) - coeff[offset] = sign - offset += 1 - end - end - else - @notimplemented - end - - N = num_components(T) - coeffs = zeros(et, length(coeff) * N, N) - @inbounds for i ∈ axes(coeffs, 2) - coeffs[i:N:end, i] = coeff - end - - return terms, coeffs +function ReferenceFE(p::Polytope, ::Bubble, ::Type{T}, order::Int; + type = :mini, coeffs = nothing, terms = nothing) where {T} + if isnothing(terms) + if type == :mini + @notimplementedif order ≠ 1 "The MINI bubble is only implemented for order 1 (ℙ1ᴰ+b–ℙ1) element." + end + else + max_orders = Tuple(maximum(Tuple(term), init=1) - 1 for term in terms) + @check order == maximum(max_orders, init=0) "`order` is not consistent with the given monomial `terms`." + end + return BubbleRefFE(T, p; type, coeffs, terms) end +function _mini_bubble_terms_and_coeffs(::Type{T}, p::Polytope{D}) where {T, D} + et = eltype(T) + if is_simplex(p) + # x_1x_2...x_D(1-x_1-x_2-...-x_D) + terms = Vector{CartesianIndex{D}}(undef, D+1) + @inbounds for j ∈ 1:D + # x_1x_2...x_{j-1}(x_j^2)x_{j+1}...x_D + terms[j] = CartesianIndex(ntuple(i->i==j ? 3 : 2, Val{D}())) + end + # x_1x_2...x_D + terms[D+1] = CartesianIndex(tfill(2, Val{D}())) + coeff = fill(-one(et), D+1) + coeff[D+1] = one(et) + elseif is_n_cube(p) + # x_1x_2...x_D(1-x_1)(1-x_2)...(1-x_D) + terms = Vector{CartesianIndex{D}}(undef, 2^D) + coeff = Vector{et}(undef, 2^D) + offset = 1 + # Loop through all terms in the binomial expansion. + @inbounds for n ∈ 0:D + sign = (-one(et))^n + for idx ∈ combinations(1:D, n) + terms[offset] = CartesianIndex(ntuple(i -> i in idx ? 3 : 2, Val{D}())) + coeff[offset] = sign + offset += 1 + end + end + else + @notimplemented + end + + N = num_indep_components(T) + coeffs = zeros(et, length(coeff) * N, N) + @inbounds for i ∈ axes(coeffs, 2) + coeffs[i:N:end, i] = coeff + end + + return terms, coeffs +end diff --git a/src/ReferenceFEs/CDLagrangianRefFEs.jl b/src/ReferenceFEs/CDLagrangianRefFEs.jl index f4b071e08..457cb8c2d 100644 --- a/src/ReferenceFEs/CDLagrangianRefFEs.jl +++ b/src/ReferenceFEs/CDLagrangianRefFEs.jl @@ -1,6 +1,14 @@ +# These shouln't be exported from ReferenceFEs +""" """ const CONT = 0 +""" """ const DISC = 1 +""" + struct CDConformity{D} <: Conformity + cont::NTuple{D,Int} + end +""" struct CDConformity{D} <: Conformity cont::NTuple{D,Int} end @@ -47,7 +55,6 @@ function _cd_lagrangian_ref_fe(::Type{T},p::ExtrusionPolytope{D},orders,cont) wh nodes, face_own_nodes = cd_compute_nodes(p,orders) dofs = LagrangianDofBasis(T,nodes) - nnodes = length(dofs.nodes) ndofs = length(dofs.dof_to_node) face_own_nodes = _compute_cd_face_own_nodes(p,orders,cont) @@ -136,7 +143,7 @@ end if active_faces[offset+iface] face = Polytope{d}(p,iface) face_ref_x = get_vertex_coordinates(face) - face_prebasis = MonomialBasis(Float64,face,1) + face_prebasis = monomial_basis(Float64,face,1) change = inv(evaluate(face_prebasis,face_ref_x)) face_shapefuns = linear_combination(change,face_prebasis) face_vertex_ids = get_faces(p,d,0)[iface] diff --git a/src/ReferenceFEs/CLagrangianRefFEs.jl b/src/ReferenceFEs/CLagrangianRefFEs.jl index a4eb860ad..65e83056f 100644 --- a/src/ReferenceFEs/CLagrangianRefFEs.jl +++ b/src/ReferenceFEs/CLagrangianRefFEs.jl @@ -1,6 +1,3 @@ -struct GradConformity <: Conformity end -const H1Conformity = GradConformity - function Conformity(reffe::GenericLagrangianRefFE{GradConformity},sym::Symbol) h1 = (:H1,:C0,:Hgrad) @@ -40,6 +37,7 @@ function get_own_nodes_permutations(reffe::GenericLagrangianRefFE{GradConformity p = get_polytope(reffe) face_own_nodes = get_face_own_nodes(reffe) dofs = get_dof_basis(reffe) + dofs = dofs isa LinearCombinationDofVector ? dofs.predofs : dofs interior_nodes = dofs.nodes[face_own_nodes[end]] compute_own_nodes_permutations(p,interior_nodes) end @@ -82,7 +80,7 @@ end # API particular to LagrangianRefFE{GradConformity} """ - ReferenceFE{N}(reffe::GenericLagrangianRefFE{GradConformity},iface::Integer) where N + ReferenceFE{N}(reffe::GenericLagrangianRefFE{GradConformity}, iface::Integer) """ function ReferenceFE{N}(reffe::GenericLagrangianRefFE{GradConformity},iface::Integer) where N reffaces = reffe.reffe.metadata @@ -97,7 +95,12 @@ end """ get_reffaces( ::Type{ReferenceFE{d}}, - reffe::GenericLagrangianRefFE{GradConformity}) where d -> Vector{GenericLagrangianRefFE{GradConformity,M,d}} + reffe::GenericLagrangianRefFE{GradConformity} + ) -> Vector{GenericLagrangianRefFE{GradConformity,d}} + +Vector of the reference FE of each `d`-face of `reffe`'s polytope. This +corresponds to the unique refference FEs that were generated by +[`compute_lagrangian_reffaces`](@ref) at the creation of `reffe`. """ function get_reffaces(::Type{ReferenceFE{d}},reffe::GenericLagrangianRefFE{GradConformity}) where d ftype_to_reffe, _ = _compute_reffes_and_face_types(reffe,Val{d}()) @@ -107,6 +110,10 @@ end """ get_face_type(reffe::GenericLagrangianRefFE{GradConformity}, d::Integer) -> Vector{Int} + +Vector of indices containing, for each face of `reffe`'s polytope, the +refference FE of that face in +[`get_reffaces(ReferenceFE{d}, reffe)`](@ref get_reffaces(::Type{ReferenceFE},)). """ function get_face_type(reffe::GenericLagrangianRefFE{GradConformity}, d::Integer) _, iface_to_ftype = _compute_reffes_and_face_types(reffe,Val{d}()) @@ -139,7 +146,7 @@ function is_P(reffe::GenericLagrangianRefFE{GradConformity}) end """ - is_Q(reffe::GenericLagrangianRefFE{GradConformity}) + is_Q(reffe::GenericLagrangianRefFE{GradConformity}) """ function is_Q(reffe::GenericLagrangianRefFE{GradConformity}) monomials = get_prebasis(reffe) @@ -148,7 +155,7 @@ function is_Q(reffe::GenericLagrangianRefFE{GradConformity}) end """ - is_S(reffe::GenericLagrangianRefFE{GradConformity}) + is_S(reffe::GenericLagrangianRefFE{GradConformity}) """ function is_S(reffe::GenericLagrangianRefFE{GradConformity}) is_n_cube(get_polytope(reffe)) && ! is_Q(reffe) @@ -192,43 +199,54 @@ end # Construction of LagrangianRefFE from Polytopes """ - LagrangianRefFE(::Type{T},p::Polytope,orders) where T - LagrangianRefFE(::Type{T},p::Polytope,order::Int) where T + LagrangianRefFE(::Type{T}, p::Polytope{D}, orders) + LagrangianRefFE(::Type{T}, p::Polytope{D}, order::Int) + +Builds a `LagrangianRefFE` object on top of `p`. `T` is the type of the value of +the approximation space (e.g., `T=Float64` for scalar-valued problems, +`T=VectorValue{N,Float64}` for vector-valued problems with `N` components). -Builds a `LagrangianRefFE` object on top of the given polytope. `T` is the type of -the value of the approximation space (e.g., `T=Float64` for scalar-valued problems, -`T=VectorValue{N,Float64}` for vector-valued problems with `N` components). The arguments `order` or `orders` -are for the polynomial order of the resulting space, which allows isotropic or anisotropic orders respectively -(provided that the cell topology allows the given anisotropic order). The argument `orders` should be an -indexable collection of `D` integers (e.g., a tuple or a vector), being `D` the number of space dimensions. +The arguments `order` and `orders` are for the polynomial order of the resulting +space, which allows isotropic or anisotropic orders respectively (provided that +the cell topology allows the given anisotropic order). The argument `orders` +should be an indexable collection of `D` integers. -In order to be able to use this function, the type of the provided polytope `p` has to implement the -following additional methods. They have been implemented for `ExtrusionPolytope` in the library. They -need to be implemented for new polytope types in order to build Lagangian reference elements on top of them. +This constructor requires `typeof(p)` to implement the following additional +methods. They are currently implemented for `ExtrusionPolytope`. -- [`compute_monomial_basis(::Type{T},p::Polytope,orders) where T`](@ref) -- [`compute_own_nodes(p::Polytope,orders)`](@ref) -- [`compute_face_orders(p::Polytope,face::Polytope,iface::Int,orders)`](@ref) +- [`compute_monomial_basis(::Type{T}, p::Polytope, orders) where T`](@ref) +- [`compute_poly_basis(::Type{T}, p::Polytope, orders; poly_type<:Polynomial) where T`](@ref) +- [`compute_own_nodes(p::Polytope, orders)`](@ref) +- [`compute_face_orders(p::Polytope, face::Polytope, iface::Int, orders)`](@ref) -The following methods are also used in the construction of the `LagrangianRefFE` object. A default implementation -of them is available in terms of the three previous methods. However, the user can also implement them for -new polytope types increasing customization possibilities. +# Extended help -- [`compute_nodes(p::Polytope,orders)`](@ref) +The keyword argument `space::Symbol` can be specified to change the default +polynomial space. The default space of the element depends on `p`, it is :P (ℙ) +for simplices and :Q (ℚ)for n-cubes. Additionally, :P and :S (serendipity) +are also available for n-cubes. + +The following methods are also used in the construction of the `LagrangianRefFE`s +and can be overloaded. + +- [`compute_nodes(p::Polytope, orders)`](@ref) - [`compute_own_nodes_permutations(p::Polytope, interior_nodes)`](@ref) -- [`compute_lagrangian_reffaces(::Type{T},p::Polytope,orders) where T`](@ref) +- [`compute_lagrangian_reffaces(::Type{T}, p::Polytope, orders) where T`](@ref) """ -function LagrangianRefFE(::Type{T},p::Polytope{D},orders;space::Symbol=_default_space(p)) where {T,D} +function LagrangianRefFE(::Type{T},p::Polytope{D},orders;space::Symbol=_default_space(p), + poly_type=Monomial) where {T,D} + if space == :P && is_n_cube(p) - return _PDiscRefFE(T,p,orders) + return _PDiscRefFE(T,p,orders,poly_type) elseif space == :S && is_n_cube(p) - SerendipityRefFE(T,p,orders) + SerendipityRefFE(T,p,orders; poly_type) else if any(map(i->i==0,orders)) && !all(map(i->i==0,orders)) + @check poly_type == Monomial "Continuous-Discontinuous element only implemented using Monomial pre-bases, got $poly_type." cont = map(i -> i == 0 ? DISC : CONT,orders) return _cd_lagrangian_ref_fe(T,p,orders,cont) else - return _lagrangian_ref_fe(T,p,orders) + return _lagrangian_ref_fe(T,p,orders,poly_type) end end end @@ -246,15 +264,15 @@ function ReferenceFE( ::Lagrangian, ::Type{T}, orders::Union{Integer,Tuple{Vararg{Integer}}}; - space::Symbol=_default_space(polytope)) where T + kwargs...) where T - LagrangianRefFE(T,polytope,orders;space=space) + LagrangianRefFE(T,polytope,orders; kwargs...) end -function _lagrangian_ref_fe(::Type{T},p::Polytope{D},orders) where {T,D} +function _lagrangian_ref_fe(::Type{T},p::Polytope{D},orders,poly_type) where {T,D} - prebasis = compute_monomial_basis(T,p,orders) + basis = compute_poly_basis(T,p,orders,poly_type) nodes, face_own_nodes = compute_nodes(p,orders) dofs = LagrangianDofBasis(T,nodes) reffaces = compute_lagrangian_reffaces(T,p,orders) @@ -276,17 +294,15 @@ function _lagrangian_ref_fe(::Type{T},p::Polytope{D},orders) where {T,D} reffe = GenericRefFE{typeof(conf)}( ndofs, p, - prebasis, + basis, # pre-basis dofs, conf, metadata, face_dofs) - GenericLagrangianRefFE(reffe,face_nodes) - end -function MonomialBasis(::Type{T},p::Polytope,orders) where T +function monomial_basis(::Type{T},p::Polytope,orders) where T compute_monomial_basis(T,p,orders) end @@ -364,14 +380,16 @@ end # Constructors taking Int -function LagrangianRefFE(::Type{T},p::Polytope{D},order::Int;space::Symbol=_default_space(p)) where {T,D} +function LagrangianRefFE(::Type{T},p::Polytope{D},order::Int;space::Symbol=_default_space(p), + poly_type=Monomial) where {T,D} + orders = tfill(order,Val{D}()) - LagrangianRefFE(T,p,orders;space=space) + LagrangianRefFE(T,p,orders; space, poly_type) end -function MonomialBasis(::Type{T},p::Polytope{D},order::Int) where {D,T} +function monomial_basis(::Type{T},p::Polytope{D},order::Int) where {D,T} orders = tfill(order,Val{D}()) - MonomialBasis(T,p,orders) + monomial_basis(T,p,orders) end function LagrangianDofBasis(::Type{T},p::Polytope{D},order::Int) where {T,D} @@ -383,17 +401,37 @@ end # for building LagrangianRefFEs in a seamless way """ - compute_monomial_basis(::Type{T},p::Polytope,orders) where T -> MonomialBasis + compute_monomial_basis(::Type{T}, p::Polytope, orders) -> MonomialBasis -Returns the monomial basis of value type `T` and order per direction described by `orders` -on top of the polytope `p`. +Returns the monomial basis of value type `T` and order per direction described +by `orders` on top of `p`. + +It is used to determine the nodes of the Lagrangian element. """ function compute_monomial_basis(::Type{T},p::Polytope,orders) where T @abstractmethod end """ - compute_own_nodes(p::Polytope{D},orders) where D -> Vector{Point{D,Float64}} + compute_poly_basis(::Type{T}, p::Polytope, orders, poly_type) + +Returns the function-space (pre-)basis of value type `T` and order per direction described +by `orders` on top of `p`. + +It is used for the default approximation space of the Lagrangian element. +It fallsback to `compute_monomial_basis(T,p,orders)` when `poly_type==Monomial`. +""" +function compute_poly_basis(::Type{T},p::Polytope,orders, poly_type) where T + @abstractmethod +end + +function compute_poly_basis(::Type{T},p::ExtrusionPolytope{D},orders,::Type{Monomial}) where {D,T} + compute_monomial_basis(T,p,orders) +end + + +""" + compute_own_nodes(p::Polytope{D}, orders) -> Vector{Point{D,Float64}} Returns the coordinates of the nodes owned by the interior of the polytope associated with a Lagrangian space with the order per direction described by `orders`. @@ -403,10 +441,10 @@ function compute_own_nodes(p::Polytope,orders) end """ - compute_face_orders(p::Polytope,face::Polytope,iface::Int,orders) + compute_face_orders(p::Polytope, face::Polytope, iface::Int, orders) Returns a vector or a tuple with the order per direction at the face `face` -of the polytope `p` when restricting the order per direction `orders` to this face. +of `p` when restricting the order per direction `orders` to this face. `iface` is the face id of `face` in the numeration restricted to the face dimension. """ function compute_face_orders(p::Polytope,face::Polytope,iface::Int,orders) @@ -414,15 +452,11 @@ function compute_face_orders(p::Polytope,face::Polytope,iface::Int,orders) end """ - compute_nodes(p::Polytope,orders) - -When called - - node_coords, face_own_nodes = compute_nodes(p,orders) + compute_nodes(p::Polytope, orders) -> node_coords, face_own_nodes -Returns `node_coords`, the nodal coordinates of all the Lagrangian nodes associated with the order per direction -`orders`, and `face_own_nodes`, being a vector of vectors indicating which nodes are owned by each of -the faces of the polytope `p`. +Returns `node_coords`, the nodal coordinates of all the Lagrangian nodes +associated with the order per direction `orders`, and `face_own_nodes`, a vector +of vectors indicating which nodes are owned by each of the faces of `p`. """ function compute_nodes(p::Polytope,orders) _compute_nodes(p,orders) @@ -432,8 +466,8 @@ end compute_own_nodes_permutations( p::Polytope, own_nodes_coordinates) -> Vector{Vector{Int}} -Returns a vector of vectors with the permutations of the nodes owned by the interior of the -polytope. +Returns a vector of vectors with the permutations of the nodes owned by the +interior of `p`, see also [`get_face_own_nodes_permutations`](@ref). """ function compute_own_nodes_permutations(p::Polytope, interior_nodes) perms = _compute_node_permutations(p, interior_nodes) @@ -441,11 +475,15 @@ function compute_own_nodes_permutations(p::Polytope, interior_nodes) end """ - compute_lagrangian_reffaces(::Type{T},p::Polytope,orders) where T + compute_lagrangian_reffaces(::Type{T}, p::Polytope{D}, orders) -Returns a tuple of length `D` being the number of space dimensions. -The entry `d+1` of this tuple contains a vector of `LagrangianRefFE` -one for each face of dimension `d` on the boundary of the polytope. +Returns a tuple of length `D`. Its `d+1` entry is a vector of `LagrangianRefFE`, +one for each face of dimension `d` on the boundary of `p`. + +These refference FEs can be used as a geometrical mappings from the reference +polytope of the corresponding face of `p` into the face itself, or the face +mapped in the physical space by the shape functions of +`LagrangianRefFE(T,p,orders;space=space)` as geometrical map. """ function compute_lagrangian_reffaces(::Type{T},p::Polytope,orders) where T _compute_lagrangian_reffaces(T,p,orders) @@ -508,7 +546,7 @@ end for iface in 1:num_faces(p,d) face = Polytope{d}(p,iface) face_ref_x = get_vertex_coordinates(face) - face_prebasis = MonomialBasis(Float64,face,1) + face_prebasis = monomial_basis(Float64,face,1) change = inv(evaluate(face_prebasis,face_ref_x)) face_shapefuns = linear_combination(change,face_prebasis) face_vertex_ids = get_faces(p,d,0)[iface] @@ -538,7 +576,7 @@ _compute_node_permutations(::Polytope{0}, interior_nodes) = [[1]] function _compute_node_permutations(p, interior_nodes) vertex_to_coord = get_vertex_coordinates(p) - lbasis = MonomialBasis(Float64,p,1) + lbasis = monomial_basis(Float64,p,1) change = inv(evaluate(lbasis,vertex_to_coord)) lshapefuns = linear_combination(change,lbasis) perms = get_vertex_permutations(p) @@ -594,9 +632,24 @@ end function compute_monomial_basis(::Type{T},p::ExtrusionPolytope{D},orders) where {D,T} extrusion = Tuple(p.extrusion) terms = _monomial_terms(extrusion,orders) - MonomialBasis{D}(T,orders,terms) + MonomialBasis(Val(D),T,orders,terms) end +function compute_poly_basis(::Type{T},p::ExtrusionPolytope{D},orders,poly_type) where {D,T} + @check allequal(orders) "Heterogeneous order only implemented for `Monomial` pre-basis." + r = iszero(D) ? 0 : first(orders) + F = if is_simplex(p) + :P + elseif is_n_cube(p) + :Q⁻ + else + @notimplemented "Non `Monomial` pre-basis only available on simplices and n-cubes" + end + cart_prod = T <: MultiValue + FEEC_poly_basis(Val(D),T,r,0,F,poly_type;cart_prod) # FᵣΛ⁰(□ᴰ) +end + + function compute_own_nodes(p::ExtrusionPolytope{D},orders) where D extrusion = Tuple(p.extrusion) if all(map(i->i==0,orders)) diff --git a/src/ReferenceFEs/CrouzeixRaviartRefFEs.jl b/src/ReferenceFEs/CrouzeixRaviartRefFEs.jl new file mode 100644 index 000000000..cc5d8b772 --- /dev/null +++ b/src/ReferenceFEs/CrouzeixRaviartRefFEs.jl @@ -0,0 +1,97 @@ +""" + struct CrouzeixRaviart <: ReferenceFEName +""" +struct CrouzeixRaviart <: ReferenceFEName end + +""" + const couzeix_raviart = CrouzeixRaviart() + +Singleton of the [`CrouzeixRaviart`](@ref) reference FE name. +""" +const crouzeix_raviart = CrouzeixRaviart() + +""" + CrouzeixRaviartRefFE(::Type{T}, p::Polytope, order::Integer) + +The `order` argument has the following meaning: the divergence of the functions in this basis +is in the ℙ space of degree `order-1`. `T` is the type of scalar components. +""" +function CrouzeixRaviartRefFE(::Type{T},p::Polytope,order::Integer) where T + D = num_dims(p) + + if is_simplex(p) && order == 1 + prebasis = MonomialBasis(Val(D),T,order,Polynomials._p_filter) + fb = MonomialBasis(Val(D-1),T,0,Polynomials._p_filter) + else + @notimplemented "Crouzet-Raviart Reference FE only available for simplices and lowest order" + end + + function fmom(φ,μ,ds) # Face moment function : σ_F(φ,μ) = 1/|F| ( ∫((φ)*μ)dF ) + D = num_dims(ds.cpoly) + facet_measure = _get_dfaces_measure(ds.cpoly, D-1) + facet_measure_1 = Gridap.Fields.ConstantField(1 / facet_measure[ds.face]) + φμ = Broadcasting(Operation(⋅))(φ,μ) + Broadcasting(Operation(*))(φμ,facet_measure_1) + end + + moments = Tuple[ + (get_dimrange(p,D-1),fmom,fb), # Face moments + ] + + return Gridap.ReferenceFEs.MomentBasedReferenceFE(crouzeix_raviart,p,prebasis,moments,L2Conformity()) +end + +function ReferenceFE(p::Polytope,::CrouzeixRaviart,::Type{T}, order) where T + CrouzeixRaviartRefFE(T,p,order) +end + + +function Conformity(reffe::GenericRefFE{CrouzeixRaviart},sym::Symbol) + if sym == :L2 + L2Conformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a CrouzeixRaviart reference FE. + + Possible values of conformity for this reference fe are $((:L2, )). + """ + end +end + +function get_face_own_dofs(reffe::GenericRefFE{CrouzeixRaviart}, conf::L2Conformity) + get_face_dofs(reffe) +end + +""" + _get_dfaces_measure(p::Polytope{D}, d::Int) + +Return the vector of `d`-volumes of the `d`-faces of `p`. +""" # TODO: Generalize +function _get_dfaces_measure(p::Polytope{D}, d::Int) where D + @notimplementedif (!is_simplex(p) || d>3) "Only implemented for simplices of dim up to 3." + measures = Float64[] + dfaces_vertices = get_face_coordinates(p,d) + for entity in dfaces_vertices + n = length(entity) + if n == 1 # The set containing one point has cardinal 1 + push!(measures, 1.0) + elseif n == 2 # Length of an edge + p1, p2 = entity + push!(measures, norm(p2-p1)) + elseif n == 3 # Area of a simplex + p1, p2, p3 = entity + v1 = p2 - p1 + v2 = p3 - p1 + area = 0.5 * norm(cross(v1, v2)) + push!(measures, area) + elseif n == 4 && d == 3 # Volume of a tetrahedron + p1, p2, p3, p4 = entity + v = (p2-p1, p3-p1, p4-p1) + t = TensorValue( Tuple(vi⋅vj for vj in v for vi in v) ) + volume = sqrt(abs(det(t)))/6 + push!(measures, volume) + end + end + measures +end + diff --git a/src/ReferenceFEs/Dofs.jl b/src/ReferenceFEs/Dofs.jl index 75201f9b0..9c67520c2 100644 --- a/src/ReferenceFEs/Dofs.jl +++ b/src/ReferenceFEs/Dofs.jl @@ -1,76 +1,122 @@ -abstract type Dof <: Map end - -# """ -# abstract type Dof <: Map - -# Abstract type representing a degree of freedom (DOF), a basis of DOFs, and related objects. -# These different cases are distinguished by the return type obtained when evaluating the `Dof` -# object on a `Field` object. See function [`evaluate_dof!`](@ref) for more details. - -# The following functions needs to be overloaded - -# - [`dof_cache`](@ref) -# - [`evaluate_dof!`](@ref) +""" + abstract type Dof <: Map -# The following functions can be overloaded optionally +Abstract type for a degree of freedom, seen as a linear form over a functional +space (typically a polynomial space). The domain is thus a [`Field`](@ref) set +and the range the scalar set. +""" +abstract type Dof <: Map end -# - [`dof_return_type`](@ref) +""" + struct LinearCombinationDofVector{T<:Dof,V,F} <: AbstractVector{T} + values :: V + predofs:: F + end -# The interface is tested with +Type that implements a dof basis (a) as the linear combination of a dof pre-basis +(b). The dofs are first evaluated at the dof pre-basis (b) (field `predofs`) and the +predof values are next mapped to dof basis (a) applying a change of basis (field +`values`). -# - [`test_dof`](@ref) +Fields: -# In most of the cases it is not strictly needed that types that implement this interface -# inherit from `Dof`. However, we recommend to inherit from `Dof`, when possible. +- `values::AbstractMatrix{<:Number}` the matrix of the change from dof basis (b) to (a) +- `predofs::AbstractVector{T}` A type representing dof pre-basis (b), with `T<:Dof` +""" +@ahe struct LinearCombinationDofVector{T,V,F} <: AbstractVector{T} + values::V + predofs::F + function LinearCombinationDofVector( + values::AbstractMatrix{<:Number}, + predofs::AbstractVector{<:Dof} + ) + @check size(values,1) == length(predofs) """\n + Incompatible sizes for performing the linear combination + + linear_combination(values,predofs) = transpose(values)*predofs + + size(values,1) != length(predofs) + """ + T = eltype(predofs) + V = typeof(values) + F = typeof(predofs) + new{T,V,F}(values,predofs) + end +end +Base.size(a::LinearCombinationDofVector) = (size(a.values,2),) +Base.IndexStyle(::LinearCombinationDofVector) = IndexLinear() +Base.getindex(::LinearCombinationDofVector{T},::Integer) where T = T() -# """ -# abstract type Dof <: Map end +function linear_combination(a::AbstractMatrix{<:Number}, b::AbstractVector{<:Dof}) + LinearCombinationDofVector(a,b) +end -# """ -# return_cache(dof,field) +function return_cache(b::LinearCombinationDofVector,field) + k = Fields.LinearCombinationMap(:) + cf = return_cache(b.predofs,field) + fx = evaluate!(cf,b.predofs,field) + ck = return_cache(k,fx,transpose(b.values)) + return cf, ck +end -# Returns the cache needed to call `evaluate_dof!(cache,dof,field)` -# """ -# function return_cache(dof::Dof,field) -# @abstractmethod -# end +function evaluate!(cache,b::LinearCombinationDofVector,field) + cf, ck = cache + k = Fields.LinearCombinationMap(:) + fx = evaluate!(cf,b.predofs,field) + return evaluate!(ck,k,fx,transpose(b.values)) +end -# """ -# evaluate_dof!(cache,dof,field) +""" + struct MappedDofBasis{T<:Dof,MT,BT} <: AbstractVector{T} + F :: MT + Σ :: BT + args + end -# Evaluates the dof `dof` with the field `field`. It can return either an scalar value or -# an array of scalar values depending the case. The `cache` object is computed with function -# [`dof_cache`](@ref). +Represents { η = σ∘F : σ ∈ Σ }, evaluated as η(φ) = σ(F(φ,args...)) where -# When a mathematical dof is evaluated on a physical field, a scalar number is returned. If either -# the `Dof` object is a basis of DOFs, or the `Field` object is a basis of fields, -# or both objects are bases, then the returned object is an array of scalar numbers. The first -# dimensions in the resulting array are for the `Dof` object and the last ones for the `Field` -# object. E.g, a basis of `nd` DOFs evaluated at physical field returns a vector of `nd` entries. -# A basis of `nd` DOFs evaluated at a basis of `nf` fields returns a matrix of size `(nd,nf)`. -# """ -# function evaluate!(cache,dof::Dof,field) -# @abstractmethod -# end +- σ : V -> R are dofs on V +- F : W -> V is a map between function spaces -# """ -# dof_return_type(dof,field) +Intended combinations would be: -# Returns the type for the value obtained with evaluating `dof` with `field`. +- Σ ⊂ V* a dof basis in the physical domain and ``F_*`` : V̂ -> V is a pushforward map. +- Σ̂ ⊂ V̂* a dof basis in the reference domain and ``(F_*)``⁻¹ : V -> V̂ is an inverse pushforward map. +""" +struct MappedDofBasis{T<:Dof,MT,BT,A} <: AbstractVector{T} + F :: MT + dofs :: BT + args :: A + + function MappedDofBasis(F::Map, dofs::AbstractVector{<:Dof}, args...) + T = eltype(dofs) + MT = typeof(F) + BT = typeof(dofs) + A = typeof(args) + new{T,MT,BT,A}(F,dofs,args) + end +end -# It defaults to +Base.size(b::MappedDofBasis) = size(b.dofs) +Base.IndexStyle(::MappedDofBasis) = IndexLinear() +Base.getindex(::MappedDofBasis{T}, ::Integer) where T = T() -# typeof(evaluate_dof(dof,field)) -# """ -# function return_type(dof::Dof,field) -# typeof(evaluate(dof,field)) -# end +function Arrays.return_cache(b::MappedDofBasis, fields) + f_cache = return_cache(b.F,fields,b.args...) + ffields = evaluate!(f_cache,b.F,fields,b.args...) + dofs_cache = return_cache(b.dofs,ffields) + return f_cache, dofs_cache +end -# Testers +function Arrays.evaluate!(cache, b::MappedDofBasis, fields) + f_cache, dofs_cache = cache + ffields = evaluate!(f_cache,b.F,fields,b.args...) + evaluate!(dofs_cache,b.dofs,ffields) +end """ - test_dof(dof,field,v;cmp::Function=(==)) + test_dof(dof,field,v; cmp::Function=(==)) Test that the `Dof` interface is properly implemented for object `dof`. It also checks if the object `dof` @@ -81,6 +127,11 @@ function test_dof(dof::Dof,field,v;cmp::Function=(==)) _test_dof(dof,field,v,cmp) end +""" + test_dof_array(dofs::AbstractArray{<:Dof}, field, v; cmp::Function=(==)) + +Like [`test_dof`](@ref) for a DoF basis. +""" function test_dof_array(dof::AbstractArray{<:Dof},field,v;cmp::Function=(==)) _test_dof(dof,field,v,cmp) end @@ -93,17 +144,3 @@ function _test_dof(dof,field,v,cmp) @test cmp(r,v) @test typeof(r) == return_type(dof,field) end - -#struct DofEval <: Map end -# -#function return_cache(k::DofEval,dof,field) -# return_cache(dof,field) -#end -# -#function evaluate!(cache,k::DofEval,dof,field) -# evaluate!(cache,dof,field) -#end -# -#function return_type(k::DofEval,dof,field) -# return_type(dof,field) -#end diff --git a/src/ReferenceFEs/DuffyQuadratures.jl b/src/ReferenceFEs/DuffyQuadratures.jl index ffe444007..453eebda8 100644 --- a/src/ReferenceFEs/DuffyQuadratures.jl +++ b/src/ReferenceFEs/DuffyQuadratures.jl @@ -1,41 +1,41 @@ +""" + struct Duffy <: QuadratureName + +Duffy quadrature rule for simplices, obtained as the mapped +tensor product of 1d Gauss-Jacobi and Gauss-Legendre quadratures. + +# Constructor: + + Quadrature(p::Polytope, duffy, degree::Integer; T::Type{<:AbstractFloat}=Float64) +""" struct Duffy <: QuadratureName end +""" + const duffy = Duffy() +""" const duffy = Duffy() -function Quadrature(p::Polytope,name::Duffy,degree::Integer;T::Type{<:AbstractFloat}=Float64) +function Quadrature(p::Polytope,::Duffy,degree::Integer;T::Type{<:AbstractFloat}=Float64) @assert is_simplex(p) D = num_dims(p) - x,w = _duffy_quad_data(degree,D,T=T) - msg = "Simplex quadrature of degree $degree obtained with Duffy transformation and tensor product of 1d Gauss-Jacobi and Gauss-Legendre rules." - GenericQuadrature(x,w,msg) + x, w = _duffy(degree,D,T=T) + name = "Simplex quadrature of degree $degree obtained with Duffy transformation and tensor product of 1d Gauss-Jacobi and Gauss-Legendre rules." + GenericQuadrature(x,w,name) end -function _duffy_quad_data(order::Integer,D::Int;T::Type{<:AbstractFloat}=Float64) - - beta = 0 - dim_to_quad_1d = [ - _gauss_jacobi_in_0_to_1(order,(D-1)-(d-1),beta;T=T) for d in 1:(D-1) ] - - quad_1d = _gauss_legendre_in_0_to_1(order;T=T) - push!(dim_to_quad_1d,quad_1d) - - x_pos = 1 - w_pos = 2 - dim_to_xs_1d = [quad_1d[x_pos] for quad_1d in dim_to_quad_1d] - dim_to_ws_1d = [quad_1d[w_pos] for quad_1d in dim_to_quad_1d] +function _duffy(degree::Integer,D::Int;T::Type{<:AbstractFloat}=Float64) + quad_1d = [ gauss_jacobi_quadrature(degree,(D-1)-(d-1),0;T) for d in 1:D] + coords_1d, weights_1d = map(first, quad_1d), map(last, quad_1d) a = T(0.5) for d in (D-1):-1:1 - ws_1d = dim_to_ws_1d[d] - ws_1d[:] *= a + weights_1d[d] .*= a a *= T(0.5) end - x,w = _tensor_product_duffy(dim_to_xs_1d,dim_to_ws_1d) - - (_duffy_map.(x),w) - + coords, weights = _tensor_product(coords_1d, weights_1d) + return _duffy_map.(coords), weights end # Duffy map from the n-cube in [0,1]^d to the n-simplex in [0,1]^d @@ -51,65 +51,3 @@ function _duffy_map(q::Point{D,T}) where {D,T} end _duffy_map(q::Point{1,T}) where T = q - -function _gauss_jacobi_in_0_to_1(order,alpha,beta;T::Type{<:AbstractFloat}=Float64) - n = _npoints_from_order(order) - x,w = gaussjacobi(n,alpha,beta) - _map_to(0,1,x,w;T=T) -end - -function _gauss_legendre_in_0_to_1(order;T::Type{<:AbstractFloat}=Float64) - n = _npoints_from_order(order) - x,w = gausslegendre(n) - _map_to(0,1,x,w;T=T) -end - -# Transforms a 1-D quadrature from `[-1,1]` to `[a,b]`, with `a0 + if k == 0 + #@check r>0 "P⁻Λᵏ starts at r=1" + scalar, r + elseif k == D + scalar, r + elseif k == 1 && !rotate_90 + nedelec2, r + else # must be k = D-1 && rotate_90 = true if k = 1 + bdm, r + end + elseif F == :Q⁻ + @check is_n_cube(p) #&& r>0 + if k == 0 + scalar, r + elseif k == D + scalar, r-1 + elseif k == 1 && !rotate_90 + nedelec1, r-1 + else # must be k = D-1 && rotate_90 = true if k = 1 + raviart_thomas, r-1 + end + elseif F == :S + @check is_n_cube(p) #&& r>0 + if k == 0 + scalar, r + elseif k == D + if nodal + return ReferenceFE(p,lagrangian,T,r; space=:P, kwargs...) # ℙᴰᵣ space ≡ SᵣΛᴰ + else + @notimplemented "Modal SrΛᴰ elements are not implemented, please use nodal ones (pass `nodal=true`)." + end + else + @notimplemented "BDM on n-cubes and Serendipity Nedelec not implemented yet" + #elseif k == 1 && !rotate_90 + # nedelec2, r + #else # must be k = D-1 && rotate_90 = true if k = 1 + # bdm, r + end + else + @unreachable + end + + ReferenceFE(p,name,T,order; kwargs...) +end diff --git a/src/ReferenceFEs/ExtrusionPolytopes.jl b/src/ReferenceFEs/ExtrusionPolytopes.jl index 044398bec..0a12e2248 100644 --- a/src/ReferenceFEs/ExtrusionPolytopes.jl +++ b/src/ReferenceFEs/ExtrusionPolytopes.jl @@ -65,7 +65,7 @@ end """ Polytope(extrusion::Int...) -Equivalent to `ExtrusionPolytope(extrusion...)` +Equivalent to [`ExtrusionPolytope(extrusion::Int...)`](@ref). """ Polytope(extrusion::Int...) = Polytope(extrusion) @@ -82,7 +82,7 @@ The values in `extrusion` are either equal to the constant # Examples -Creating a quadrilateral, a triangle, and a wedge +Creating a quadrilateral, a triangle, and a wedge (triangular prism) ```jldoctest using Gridap.ReferenceFEs @@ -327,16 +327,16 @@ function simplexify_hypercube(dim::Int) end """ -Sweep through the `dim`-dimensional hypercube recursively, collecting +Sweep through the `D`-Densional hypercube recursively, collecting all simplices. -We represent vertices as bit patterns. In `dim` dimensions, the -lowermost `dim` bits are either zero or one. Interpreted as integer, +We represent vertices as bit patterns. In `D` Densions, the +lowermost `D` bits are either zero or one. Interpreted as integer, this labels the vertices of the hypercube from the origin ("bottom") -`0` to the diagonally opposite vertex ("top") `2^dim-1`. +`0` to the diagonally opposite vertex ("top") `2^D-1`. Each simplex contains both the bottom vertex `0` as well as the top -vertex `2^dim-1`. Its other vertices trace a path from the bottom to +vertex `2^D-1`. Its other vertices trace a path from the bottom to the top. The algorithm below finds all possible paths. - `simplices` is the accumulator where the simplices are collected. @@ -390,7 +390,7 @@ end """ get_extrusion(p::ExtrusionPolytope) -Equivalent to `p.extrusion`. +Return the vector of the extrusions defining `p`. See also [`ExtrusionPolytope`](@ref). """ get_extrusion(p::ExtrusionPolytope) = p.extrusion @@ -724,7 +724,7 @@ function _nullspace(v) end # Returns the first vertex not belonging to the facet i_f, or -1 if all vertices -# belong to the facet. +# belong to the facet. # nf_vs is the array of arrays of vertices of the facets of the polytope. function _vertex_not_in_facet(p::DFace, i_f, nf_vs) for i in p.nf_dimranges[end][1] @@ -893,7 +893,6 @@ const VERTEX = Polytope() """ const SEGMENT = Polytope(HEX_AXIS) -# TODO use larger names """ const TRI = Polytope(TET_AXIS,TET_AXIS) """ diff --git a/src/ReferenceFEs/GeneralPolytopes.jl b/src/ReferenceFEs/GeneralPolytopes.jl index 3396d79a6..7ea01b6c3 100644 --- a/src/ReferenceFEs/GeneralPolytopes.jl +++ b/src/ReferenceFEs/GeneralPolytopes.jl @@ -1,21 +1,21 @@ """ struct GeneralPolytope{D,Dp,Tp} <: Polytope{D} - The `GeneralPolytope` is definded defined by a set of vertices and a rototation - system (a planar oriented graph). This polytopal representation can represent - any polytope in 2 and 3 dimensions. +The `GeneralPolytope` is definded defined by a set of vertices and a rotation +system (a planar oriented graph). This polytopal representation can represent +any polytope in 2 and 3 dimensions. - In 2 dimensions ([`Polygon`](@ref)), the representation of the polygon is a closed polyline. +In 2 dimensions ([`Polygon`](@ref)), the representation of the polygon is a closed polyline. - In 3 dimensions ([`Polyhedron`](@ref)), the rotation system generates the connectivities, each facet is a closed cycle of the graph. - This construction allows complex geometrical operations, e.g., intersecting polytopes by halfspaces. - See also, +In 3 dimensions ([`Polyhedron`](@ref)), the rotation system generates the connectivities, each facet is a closed cycle of the graph. +This construction allows complex geometrical operations, e.g., intersecting polytopes by halfspaces. +See also, - > K. Sugihara, "A robust and consistent algorithm for intersecting convex polyhedra", Comput. Graph. Forum 13 (3) (1994) 45–54, doi: [10.1111/1467-8659.1330045](https://doi.org/10.1111/1467-8659.1330045) +> K. Sugihara, "A robust and consistent algorithm for intersecting convex polyhedra", Comput. Graph. Forum 13 (3) (1994) 45–54, doi: [10.1111/1467-8659.1330045](https://doi.org/10.1111/1467-8659.1330045) - > D. Powell, T. Abel, "An exact general remeshing scheme applied to physically conservative voxelization", J. Comput. Phys. 297 (Sept. 2015) 340–356, doi: [10.1016/j.jcp.2015.05.022](https://doi.org/10.1016/j.jcp.2015.05.022. +> D. Powell, T. Abel, "An exact general remeshing scheme applied to physically conservative voxelization", J. Comput. Phys. 297 (Sept. 2015) 340–356, doi: [10.1016/j.jcp.2015.05.022](https://doi.org/10.1016/j.jcp.2015.05.022. - > S. Badia, P. A. Martorell, F. Verdugo. "Geometrical discretisations for unfitted finite elements on explicit boundary representations", J.Comput. Phys. 460 (2022): 111162. doi: [10.1016/j.jcp.2022.111162](https://doi.org/10.1016/j.jcp.2022.111162) +> S. Badia, P. A. Martorell, F. Verdugo. "Geometrical discretisations for unfitted finite elements on explicit boundary representations", J.Comput. Phys. 460 (2022): 111162. doi: [10.1016/j.jcp.2022.111162](https://doi.org/10.1016/j.jcp.2022.111162) """ struct GeneralPolytope{D,Dp,Tp,Td} <: Polytope{D} vertices::Vector{Point{Dp,Tp}} @@ -50,14 +50,14 @@ end """ Polygon = GeneralPolytope{2} - A polygon is a [`GeneralPolytope`](@ref) in 2 dimensions. +A polygon is a [`GeneralPolytope`](@ref) in 2 dimensions. """ const Polygon = GeneralPolytope{2} """ Polyhedron = GeneralPolytope{3} - A polyhedron is a [`GeneralPolytope`](@ref) in 3 dimensions. +A polyhedron is a [`GeneralPolytope`](@ref) in 3 dimensions. """ const Polyhedron = GeneralPolytope{3} @@ -95,10 +95,10 @@ function GeneralPolytope{D}( end """ - GeneralPolytope{D}(vertices,graph;kwargs...) + GeneralPolytope{D}(vertices, graph; kwargs...) - Constructor of a [`GeneralPolytope`](@ref) that generates a polytope of - D dimensions with the given `vertices` and `graph` of connectivities. +Constructor of a [`GeneralPolytope`](@ref) that generates a polytope of +D dimensions with the given `vertices` and `graph` of connectivities. """ function GeneralPolytope{D}( vertices::AbstractVector{<:Point}, @@ -201,28 +201,28 @@ Base.getindex(a::GeneralPolytope,i::Integer) = a.vertices[i] """ get_graph(p::GeneralPolytope) -> Vector{Vector{Int32}} - It returns the edge-vertex graph of the polytope `p`. +Returns the edge-vertex graph of `p`. """ @inline get_graph(a::GeneralPolytope) = a.edge_vertex_graph """ get_metadata(p::GeneralPolytope) - It return the metadata stored in the polytope `p`. +Return the metadata stored in `p`. """ get_metadata(a::GeneralPolytope) = a.metadata """ isopen(p::GeneralPolytope) -> Bool - In return whether the polytope is watter tight or not. +Return whether `p` is watertight or not. """ Base.isopen(a::GeneralPolytope) = a.isopen """ - isactive(p::GeneralPolytope,vertex::Integer) -> Bool + isactive(p::GeneralPolytope, vertex::Integer) -> Bool - It returns whether a vertex is connected to any other vertex. +Returns whether `p`'s vertex of index `vertex` is connected to any other vertex of `p`. """ function isactive(p::GeneralPolytope,vertex::Integer) !isempty( get_graph(p)[vertex] ) @@ -231,8 +231,7 @@ end """ check_polytope_graph(p::GeneralPolytope) -> Bool - It checks whether the graph is well-constructed. The graph must be oriented - and planar. +It checks whether `p`'s graph is well-constructed, i.e. if it is oriented and planar. """ function check_polytope_graph(p::GeneralPolytope) check_polytope_graph(get_graph(p)) @@ -648,11 +647,12 @@ function simplexify_interior(p::Polygon) end """ - simplexify_interior(p::Polyhedron) + simplexify_interior(p::Polyhedron) -> (coords, triangles) - `simplex_interior` computes a simplex partition of the volume inside - the Polyhedron `p`. - It returns a vector of coordinates and an array of connectivitties. +`simplex_interior` computes a simplex partition of the volume inside the Polyhedron `p`. + +Returns a vector of coordinates `coords` and a vector `triangles` containing the +connectivitty vectors defining each triangle of the partition. """ function simplexify_interior(poly::Polyhedron) !isopen(poly) || return simplexify_surface(poly) @@ -705,11 +705,12 @@ function simplexify_interior(poly::Polyhedron) end """ - simplexify_surface(p::Polyhedron) + simplexify_surface(p::Polyhedron) -> (coords, triangles) + +Computes a simplex partition of the boundary of `p`. - `simplex_surface` computes a simplex partition of the surface bounding - the Polyhedron `p`. - It returns a vector of coordinates and an array of connectivitties. +Returns a vector of coordinates `coords` and a vector `triangles` containing the +connectivitty vectors defining each triangle of the partition. """ function simplexify_surface(poly::Polyhedron) istouch = map( i -> falses(length(i)), get_graph(poly) ) @@ -747,9 +748,9 @@ function compute_orientation(p::GeneralPolytope{D}) where D return s end -# Admissible permutations for Polygons are the ones that +# Admissible permutations for Polygons are the ones that # preserve the orientation of the circular graph that defines it. -# For 2D polytopes, this will always be positive. For 3D polytopes, i.e +# For 2D polytopes, this will always be positive. For 3D polytopes, i.e # faces of a polyhedron, the orientation can also be negative. function get_vertex_permutations(p::GeneralPolytope{2}) base = collect(1:num_vertices(p)) @@ -785,7 +786,7 @@ end """ renumber!(graph::Vector{Vector{Int32}},new_to_old::Vector{Int},n_old::Int) -Given a polyhedron graph, renumber the nodes of the graph using the `new_to_old` mapping. +Given a polyhedron graph, renumber the nodes of the graph using the `new_to_old` mapping. Removes the empty nodes. """ function renumber!(graph::Vector{Vector{Int32}},new_to_old::Vector{Int},n_old::Int) @@ -845,28 +846,28 @@ end merge_polytopes(p1::GeneralPolytope{D},p2::GeneralPolytope{D},f1,f2) Merge polytopes `p1` and `p2` by gluing the faces `f1` and `f2` together. -The faces `f1` and `f2` need to be given as list of nodes in the same order. +The faces `f1` and `f2` need to be given as list of nodes in the same order. I.e we assume that `get_vertex_coordinates(p1)[f1[k]] == get_vertex_coordinates(p2)[f2[k]]` for all `k`. -# Algorithm: +# Algorithm: - Polyhedrons have planar graphs, with each face represented by an oriented closed path. -- Visually, this means we can glue boths polytopes `p1` and `p2` by drawing the graph `G2` inside the -closed path of the face `f1` of `G1`. We can them add edges between the vertices of the closed paths +- Visually, this means we can glue boths polytopes `p1` and `p2` by drawing the graph `G2` inside the +closed path of the face `f1` of `G1`. We can them add edges between the vertices of the closed paths of `f1` and `f2`, then collapse the edges to create the final graph. -- To create the edge `(i1,i2)`: - + Around each node, its neighbors are oriented in a consistent way. This - means that for a selected face (closed path), there will always be two consecutive neighbors that +- To create the edge `(i1,i2)`: + + Around each node, its neighbors are oriented in a consistent way. This + means that for a selected face (closed path), there will always be two consecutive neighbors that belong to the selected face. - + To create the new edge, we insert the new neighbor between the two consecutive neighbors of the + + To create the new edge, we insert the new neighbor between the two consecutive neighbors of the selected face. This will consistently embed `G2` into `f1`. """ function merge_polytopes(p1::Polyhedron,p2::Polyhedron,f1,_f2) @check isequal(length(f1),length(_f2)) - + offset = num_vertices(p1) f2 = _f2 .+ offset graph = deepcopy(get_graph(p1)) @@ -910,7 +911,7 @@ end function merge_polytopes(p1::Polygon,p2::Polygon,f1,f2) @check length(f1) == length(f2) == 2 - if f1[1] > f1[2] # Reversed edge + if f1[1] > f1[2] # Reversed edge @assert f2[2] > f2[1] return merge_polytopes(p2,p1,f2,f1) end @@ -953,7 +954,7 @@ function polygon_from_faces( perm[k] = graph[perm[k-1]][2] end permute!(vertices,perm) - + @check check_polytope_graph(graph) return Polygon(vertices), perm end diff --git a/src/ReferenceFEs/GeometricDecompositions.jl b/src/ReferenceFEs/GeometricDecompositions.jl new file mode 100644 index 000000000..400ae0806 --- /dev/null +++ b/src/ReferenceFEs/GeometricDecompositions.jl @@ -0,0 +1,384 @@ +################################ +# Geometric decomposition APIs # +################################ + +""" + has_geometric_decomposition(shapefuns, p::Polytope, ::Conformity) -> Bool + +Tells whether `shapefuns` is a geometrically decomposed basis on `p` for the +given conformity. This is always true for `L2Conformity()`. + +Otherwise, the decomposition is defined relatively to the appropriate trace: +- For `GradConformity()`, the trace is the restriction to the face, defined on all boundary faces, +- For `CurlConformity()`, the trace is the tangential trace to edges and tangential component to 2D facets, +- For `DivConformity()`, the trace is the normal trace to facets (rotated tangents in 2D). +""" +has_geometric_decomposition(shapefuns, p::Polytope, ::Conformity) = false +has_geometric_decomposition(shapefuns, p::Polytope, ::L2Conformity) = true + +""" + get_face_own_funs(shapefuns, p::Polytope, ::Conformity) -> Vector{Vector{Int}} + +Essentially the same as [`get_face_own_dofs`](@ref), but for the `shapefuns` +basis instead of a DoF basis. + +`shapefun` must implement the geometric decomposition on `p` for the given +conformity, this can be checked using [`has_geometric_decomposition`](@ref). +""" +get_face_own_funs(shapefuns, ::Polytope, ::Conformity) = @abstractmethod + +function get_face_own_funs(shapefuns, p::Polytope, ::L2Conformity) + _l2_conforming_own_funs(shapefuns,p) +end + +function _l2_conforming_own_funs(shapefuns,p) + r = [Int[] for _ in 1:num_faces(p)] + r[end] = collect(1:length(shapefuns)) + r +end + +""" + get_facet_flux_sign_flip(shapefun, p::Polytope, ::DivConformity) + +Return the (diagonal) change of basis matrix to make the flux of facet-owned +polynomials of `b` be oriented outwards the facet. + +`shapefun` must implement the geometric decomposition on `p` for `DivConformity()`, +this can be checked using [`has_geometric_decomposition`](@ref). + +# Extended help + +The gluing of div-conforming shape functions assumes that all facet-owned +shapefuns have consistent orientation between facets (if the first shapefun of +facet 1 has outwards flux, all first shapefun of other facets must also have +outwards flux), see also [`NormalSignMap`](@ref Gridap.FESpaces.NormalSignMap). + +This is not the case for the `BarycentricP(m)ΛBases` by default, their flux is +oriented like the sign of the permutation of the facet node indices. +""" +get_facet_flux_sign_flip(shapefuns, ::Polytope, ::L2Conformity) = @abstractmethod + + +############################################################## +# Geometric decompositions of barycentric bases on simplices # +############################################################## + +# BernsteinBasisOnSimplex + +function has_geometric_decomposition( + b::BernsteinBasisOnSimplex{D}, p::Polytope, conf::Conformity) where D + + conf isa L2Conformity && return true + + !is_simplex(p) || D != num_dims(p) && return false + if !_are_barycoords_relative_to_simplex(b, p) + @warn """ + The barycentric coordinates of the given basis is not defined relative to the given simplex vertices. + $(sprint(Base.show_backtrace, stacktrace())) + """ + return false + end + + conf isa GradConformity && return true + + false +end + +function get_face_own_funs( + b::BernsteinBasisOnSimplex{D,V}, p::Polytope, conf::GradConformity) where {D,V} + + @check has_geometric_decomposition(b,p,conf) + + faces = get_faces(p) + n_faces = length(faces) + face_own_funs = Vector{Int}[ Int[] for _ in 1:n_faces] + + K = get_order(b) + ncomp = num_indep_components(V) + id = 1 + for α in bernstein_terms(K,D) + F = findall(>(0), α) # vertices of the face owning x_α + face = findfirst(face -> F⊆face, faces) # p face number + # this should be guaranteed by has_geometric_decomposition + isnothing(face) && @unreachable + # the ncomp components holding the Bα scalar shapefun are contiguous due + # to Polynomials._cartprod_set_value! + append!(face_own_funs[face], id:id+ncomp-1) + id += ncomp + end + + face_own_funs +end + +function _are_barycoords_relative_to_simplex( + b::BernsteinBasisOnSimplex{D}, simplex::Polytope) where D + + @check is_simplex(simplex) && D == num_dims(simplex) + + vertices = get_vertex_coordinates(simplex) + M = b.cart_to_bary_matrix + vλ = [ Polynomials._cart_to_bary(v, M) for v in vertices ] # return SVectors ... + + T = eltype(eltype(vλ)) + V = VectorValue{D+1,T} + I_cols = component_basis(V) + vλ = reinterpret(V, vλ) + + vλ ≈ I_cols +end + +# BarycentricP(m)ΛBasis + +_BaryPΛBasis = Polynomials._BaryPΛBasis +function _is_rotated_90(ids::Polynomials.BarycentricPΛIndices) + comps = ids.components + length(comps) !== 2 && return false # only for 2D + comps[2][3] < 0 # sign of second component flipped +end + +function has_geometric_decomposition(b::_BaryPΛBasis, p::Polytope, conf::Conformity) + D = get_dimension(b) + k = b.k # form order + + conf isa L2Conformity && return true + + !is_simplex(p) || D != num_dims(p) && return false + if !_are_barycoords_relative_to_simplex(b.scalar_bernstein_basis, p) + @warn """ + The barycentric coordinates of the given basis is not defined relative to the given simplex vertices. + $(sprint(Base.show_backtrace, stacktrace())) + """ + return false + end + + conf isa GradConformity && k == 0 && return true + is_rotated_90 = _is_rotated_90(b._indices) + conf isa CurlConformity && k == 1 && !is_rotated_90 && return true + correct_rotate = D==2 ? is_rotated_90 : true + conf isa DivConformity && k == D-1 && correct_rotate && return true + + false +end + +function get_face_own_funs(b::_BaryPΛBasis, p::Polytope, conf::Conformity) + @check has_geometric_decomposition(b,p,conf) + + conf isa L2Conformity && return _l2_conforming_own_funs(b,p) + + faces = get_faces(p) + n_faces = length(faces) + face_own_funs = Vector{Int}[ Int[] for _ in 1:n_faces] + + for (F, bubble_functions) in get_bubbles(b) + face = findfirst(face -> F⊆face, faces) + # this should be guaranteed by has_geometric_decomposition + isnothing(face) && @unreachable + # Polynomials._check_PΛ_indices guaranties bubble shapefun ids are contiguous + w_first = first(bubble_functions)[1] + w_last = last( bubble_functions)[1] + face_own_funs[face] = collect(w_first:w_last) + end + + face_own_funs +end + +function get_facet_flux_sign_flip( + b::_BaryPΛBasis, p::Polytope{D}, conf::DivConformity) where D + + facet_range = get_dimrange(p,D-1) + face_own_funs = get_face_own_funs(b,p,conf) + sign_flip = MVector(tfill(1, Val(length(b)))...) + + for (face, own_funs) in enumerate(face_own_funs) + if face ∈ facet_range + sign_flip[own_funs] .= iseven(face-first(facet_range)) ? -1 : 1 + # Equivalent definition: + # F = get_faces(p)[face][1:D] + # sign_flip[own_funs] .= -Polynomials._combination_sign(F)) + end + end + + sign_flip = Diagonal(sign_flip) +end + + +############################################################### +# Geometric decompositions of tensor product bases on n-cubes # +############################################################### + +#Polynomial bases admitting a 1D geometric decomposition on the SEGMENT, currently `ModalC0` and `Bernstein`. +const GD_1D_PT = Union{Polynomials.ModalC0, Bernstein} + +# says which poly of the Kth order 1D basis does the SEGMENT vertices own +_SEGMENT_vertex_own_fun(::Type{Polynomials.ModalC0}, K) = (1, 2) # those are 1-x and x +_SEGMENT_vertex_own_fun(::Type{Bernstein}, K) = (1, K+1) # those are (1-x)ᴷ and xᴷ + +const _V0 = 1 # SEGMENT first vertex +const _V1 = 2 # SEGMENT second vertex +const _Vi = 3 # SEGMENT interior + +function _compute_fixed_coords_to_face(p,::Val{D}) where D + face_vertices = get_face_coordinates(p) + fixed_coords_to_face = Dict{NTuple{D,Int},Int}() + for (face,face_verts) in enumerate(face_vertices) + fixed_coords = ntuple( i -> + all(v->iszero(v[i]),face_verts) ? _V0 : + all(v->isone( v[i]),face_verts) ? _V1 : + _Vi, Val(D)) + fixed_coords_to_face[fixed_coords] = face + end + fixed_coords_to_face +end + +@inline function _is_poly_reference_D_cube(p,D) + !(is_n_cube(p) && D == num_dims(p)) && return false + if D<4 + DCUBE = (VERTEX, SEGMENT, QUAD, HEX)[D+1] + else + DCUBE = ExtrusionPolytope(tfill(HEX_AXIS,Val(D))) + end + # Our 1D polynomial evaluations are decomposed in [0,1]ᴰ, so the vertices of + # p must be the same as the Reference D-cube + DCUBE_vertices = get_vertex_coordinates(DCUBE) + p_vertices = get_vertex_coordinates(p) + all(∈(DCUBE_vertices), p_vertices) +end + + +# CartProdPolyBasis + +function has_geometric_decomposition( + b::CartProdPolyBasis{D,V,<:GD_1D_PT}, p::Polytope, conf::Conformity) where {D,V} + + conf isa L2Conformity && return true + + !_is_poly_reference_D_cube(p,D) && return false + + conf isa GradConformity && minimum(b.orders) > 0 && return true + + # # could be generalized to Curl and Div conformity in this case, although + # # it is quite redundant with `CompWiseTensorPolyBasis` if terms filtering + # # is implemented for it + # V <: VectorValue{D} && minimum(b.orders) > 0 && return true + + false +end + +function get_face_own_funs( + b::CartProdPolyBasis{D,V,PT}, p::Polytope, conf::GradConformity) where {D,V,PT<:GD_1D_PT} + + @check has_geometric_decomposition(b,p,conf) + + face_own_funs = Vector{Int}[ Int[] for _ in 1:num_faces(p) ] + fixed_coords_to_face = _compute_fixed_coords_to_face(p,Val(D)) + + K = get_order(b) + s0_owned, s1_owned = _SEGMENT_vertex_own_fun(PT,K) + ncomp = num_indep_components(V) + id = 1 + for ci in b.terms + own_coords = ntuple( i -> ci[i] == s0_owned ? _V0 : ci[i] == s1_owned ? _V1 : _Vi, Val(D)) + face = fixed_coords_to_face[own_coords] + append!(face_own_funs[face], id:id+ncomp-1) + id += ncomp + end + + face_own_funs +end + +# CompWiseTensorPolyBasis + +function has_geometric_decomposition( + b::CompWiseTensorPolyBasis{D,V,<:GD_1D_PT}, p::Polytope, conf::Conformity) where {D,V} + + conf isa L2Conformity && return true + + !(V <: VectorValue{D}) && return false + !_is_poly_reference_D_cube(p,D) && return false + + orders = MMatrix{D,D}(b.orders) + conf isa GradConformity && minimum(orders) > 0 && return true + conf isa CurlConformity && minimum(orders+I) > 0 && return true + conf isa DivConformity && minimum(diag(orders)) > 0 && return true + + false +end + +function get_face_own_funs( + b::CompWiseTensorPolyBasis{D,V,PT}, p::Polytope, conf::Conformity) where {D,V,PT<:GD_1D_PT} + + @check has_geometric_decomposition(b,p,conf) + conf isa L2Conformity && return _l2_conforming_own_funs(b,p) + + face_own_funs = Vector{Int}[ Int[] for _ in 1:num_faces(p) ] + fixed_coords_to_face = _compute_fixed_coords_to_face(p,Val(D)) + + # For Curl and Div conformity, some faces cannot own any shape functions, + # e.g. the faces orthogonal to eₓ cannot own a Curl-conform shape function with + # non-zero eₓ components, so _compute_mask(Val(D), 1, CurlConformity()) returns + # (true, false, ..., false) + # to indicate that the ownership along x-axis is ignored for the first component. + # This also ensures that no vertex can own a Curl-conforming function and that + # only facets and interior can own a Div-conforming function, as expected. + function _compute_mask(VD, d, conf) + conf isa GradConformity && return MVector(tfill(true, VD)) + conf isa CurlConformity && return MVector(ntuple(i -> i==d, VD)) + conf isa DivConformity && return MVector(ntuple(i -> i!=d, VD)) + end + + K = get_order(b) + s0_owned, s1_owned = _SEGMENT_vertex_own_fun(PT,K) + id = 1 + for (d,terms) in enumerate(Polynomials.get_comp_terms(b)) + mask = _compute_mask(Val(D),d,conf) + for ci in terms + own_coords = ntuple( + i -> mask[i] ? _Vi : + ci[i] == s0_owned ? _V0 : + ci[i] == s1_owned ? _V1 : + _Vi, Val(D)) + face = fixed_coords_to_face[own_coords] + + push!(face_own_funs[face], id) + id += 1 + end + end + face_own_funs +end + +function get_facet_flux_sign_flip( + b::CompWiseTensorPolyBasis{D,V,PT}, p::Polytope, conf::Conformity) where {D,V,PT<:GD_1D_PT} + + facet_range = get_dimrange(p,D-1) + face_own_funs = get_face_own_funs(b,p,conf) + sign_flip = MVector(tfill(1, Val(length(b)))...) + + for (face, own_funs) in enumerate(face_own_funs) + if face ∈ facet_range + # empirically determined, see tests + sign_flip[own_funs] .= iseven(face-first(facet_range)) ? -1 : 1 + end + end + + sign_flip = Diagonal(sign_flip) +end + + +# Helper + +function _validate_change_dof(change_dof, shapefuns, p, conf; dowarn=true) + if change_dof && !has_geometric_decomposition(shapefuns, p, conf) + dowarn && @warn """ + `change_dof=true` was requested, but the constructed basis do not implement the + geometric decomposition, falling back to `change_dof=false`. + + p: $p, + conformity: $conf, + basis: $shapefuns, + $(sprint(Base.show_backtrace, stacktrace())) + """ + return false + end + change_dof +end + diff --git a/src/ReferenceFEs/HHJRefFEs.jl b/src/ReferenceFEs/HHJRefFEs.jl new file mode 100644 index 000000000..4ed10b1c1 --- /dev/null +++ b/src/ReferenceFEs/HHJRefFEs.jl @@ -0,0 +1,103 @@ + +struct HellanHerrmannJhonson <: ReferenceFEName end + +const hhj = HellanHerrmannJhonson() + +Pushforward(::Type{HellanHerrmannJhonson}) = DoubleContraVariantPiolaMap() + +""" + struct HellanHerrmannJhonson <: ReferenceFEName end + HellanHerrmannJhonsonRefFE(::Type{T},p::Polytope,order::Integer) where T + +Hellan-Herrmann-Jhonson reference finite element. + +References: + +- `The Hellan-Herrmann-Johnson method with curved elements`, Arnold and Walker (2020) + +""" +function HellanHerrmannJhonsonRefFE(::Type{T},p::Polytope,order::Integer) where T + @notimplementedif p == TET + @assert p == TRI "HellanHerrmannJhonson Reference FE only defined for TRIangles and TETrahedra" + + D = 2 # num_dims(p) + VT = SymTensorValue{D,T} + prebasis = MonomialBasis(Val(D),VT,order,Polynomials._p_filter) + fb = MonomialBasis(Val(D-1),T,order,Polynomials._p_filter) + cb = MonomialBasis(Val(D),VT,order-1,Polynomials._p_filter) + # TODO clean this + #fb = MonomialBasis(Val(1),T,order,Polynomials._p_filter) + #fb = get_shapefuns(LagrangianRefFE(T, SEGMENT, order)) + #cb = MonomialBasis(Val(2),VT,order-1,Polynomials._p_filter) + #cb = get_shapefuns(LagrangianRefFE(VT, TRI, order-1)) + #cb = map(constant_field,[VT(0.,1.,0.),VT(-2.,1.,0.),VT(0.,-1.,2.)]) + + function cmom(φ,μ,ds) # Cell and Node moment function: σ_K(φ,μ) = ∫(φ:μ)dK + Broadcasting(Operation(⊙))(φ,μ) + end + function fmom(φ,μ,ds) # Face moment function (normal) : σ_F(φ,μ) = ∫((n·φ·n)*μ)dF + n = get_facet_normal(ds) + φn = Broadcasting(Operation(⋅))(φ,n) + nφn = Broadcasting(Operation(⋅))(n,φn) + Broadcasting(Operation(*))(nφn,μ) + end + + moments = Tuple[ + (get_dimrange(p,1),fmom,fb), # Face moments + ] + if order > 0 + push!(moments,(get_dimrange(p,2),cmom,cb)) # Cell moments + end + + return MomentBasedReferenceFE(hhj,p,prebasis,moments,DivConformity()) +end + +function ReferenceFE(p::Polytope,::HellanHerrmannJhonson,::Type{T}, order) where T + HellanHerrmannJhonsonRefFE(T,p,order) +end + +function Conformity(reffe::GenericRefFE{HellanHerrmannJhonson},sym::Symbol) + hdiv = (:Hdiv,:HDiv) + if sym == :L2 + L2Conformity() + elseif sym in hdiv + DivConformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a HellanHerrmannJhonson reference FE. + + Possible values of conformity for this reference fe are $((:L2, hdiv...)). + """ + end +end + +function get_face_own_dofs(reffe::GenericRefFE{HellanHerrmannJhonson}, conf::DivConformity) + reffe.face_dofs +end + +# TODO this is currently exactly the function from Nedelec, should be modified or factorized +function get_face_dofs(reffe::GenericRefFE{HellanHerrmannJhonson,Dc}) where Dc + face_dofs = [Int[] for i in 1:num_faces(reffe)] + face_own_dofs = get_face_own_dofs(reffe) + p = get_polytope(reffe) + for d = 1:Dc # Starting from edges, vertices do not own DoFs for Nedelec + first_face = get_offset(p,d) + nfaces = num_faces(reffe,d) + for face = first_face+1:first_face+nfaces + for df = 1:d-1 + face_faces = get_faces(p,d,df) + first_cface = get_offset(p,df) + for cface in face_faces[face-first_face] + cface_own_dofs = face_own_dofs[first_cface+cface] + for dof in cface_own_dofs + push!(face_dofs[face],dof) + end + end + end + for dof in face_own_dofs[face] + push!(face_dofs[face],dof) + end + end + end + face_dofs +end diff --git a/src/ReferenceFEs/LagrangianDofBases.jl b/src/ReferenceFEs/LagrangianDofBases.jl index cd882aa10..ec9cbd7f6 100644 --- a/src/ReferenceFEs/LagrangianDofBases.jl +++ b/src/ReferenceFEs/LagrangianDofBases.jl @@ -3,22 +3,24 @@ struct PointValue{P} <: Dof end """ - struct LagrangianDofBasis{P,V} <: AbstractArray{<:Dof} + struct LagrangianDofBasis{P,V} <: AbstractArray{PointValue{P}} nodes::Vector{P} dof_to_node::Vector{Int} dof_to_comp::Vector{Int} node_and_comp_to_dof::Vector{V} end -Type that implements a Lagrangian dof basis. +Type that implements a Lagrangian dof basis, that is a [`Dof`](@ref) basis where +the DoFs are nodal evaluations. If the value type `V` is not scalar +([`::MultiValue`](@ref MultiValue)'d), several DoFs may be associated to the same +node, one for each independant component of `V`. Fields: -- `nodes::Vector{P}` vector of points (`P<:Point`) storing the nodal coordinates -- `node_and_comp_to_dof::Vector{V}` vector such that `node_and_comp_to_dof[node][comp]` returns the dof associated with node `node` and the component `comp` in the type `V`. -- `dof_to_node::Vector{Int}` vector of integers such that `dof_to_node[dof]` returns the node id associated with dof id `dof`. -- `dof_to_comp::Vector{Int}` vector of integers such that `dof_to_comp[dof]` returns the component id associated with dof id `dof`. - +- `nodes` vector of points ([`P<:Point`](@ref Point)) storing the nodal coordinates +- `node_and_comp_to_dof` vector such that `node_and_comp_to_dof[node][comp]` returns the dof associated with node `node` and the component `comp` in the type `V`. +- `dof_to_node` vector of integers such that `dof_to_node[dof]` returns the node id associated with dof id `dof`. +- `dof_to_comp` vector of integers such that `dof_to_comp[dof]` returns the component id associated with dof id `dof`. """ struct LagrangianDofBasis{P,V} <: AbstractVector{PointValue{P}} nodes::Vector{P} @@ -44,16 +46,21 @@ function LagrangianDofBasis(dofs::LagrangianDofBasis{P},nodes::Vector{P}) where end """ - LagrangianDofBasis(::Type{T},nodes::Vector{<:Point}) where T + LagrangianDofBasis(::Type{V}, nodes::Vector{<:Point}) -Creates a `LagrangianDofBasis` for fields of value type `T` associated +Creates a `LagrangianDofBasis` for fields of value type `V` associated with the vector of nodal coordinates `nodes`. """ -function LagrangianDofBasis(::Type{T},nodes::Vector{<:Point}) where T - r = _generate_dof_layout_node_major(T,length(nodes)) +function LagrangianDofBasis(::Type{V},nodes::Vector{<:Point}) where V + r = _generate_dof_layout_node_major(V,length(nodes)) LagrangianDofBasis(nodes,r...) end +""" + get_nodes(b::LagrangianDofBasis) + +Get the vector of DoF nodes of `b`. +""" get_nodes(b::LagrangianDofBasis) = b.nodes get_dof_to_node(b::LagrangianDofBasis) = b.dof_to_node get_dof_to_comp(b::LagrangianDofBasis) = b.dof_to_comp @@ -67,13 +74,13 @@ function _generate_dof_layout_node_major(::Type{<:Real},nnodes::Integer) end # Node major implementation -function _generate_dof_layout_node_major(::Type{T},nnodes::Integer) where T<:MultiValue - V = change_eltype(T,Int) - ncomps = num_indep_components(T) +function _generate_dof_layout_node_major(::Type{V},nnodes::Integer) where V<:MultiValue + Vi = change_eltype(V,Int) + ncomps = num_indep_components(Vi) ndofs = ncomps*nnodes dof_to_comp = zeros(Int,ndofs) dof_to_node = zeros(Int,ndofs) - node_and_comp_to_dof = Vector{V}(undef,nnodes) + node_and_comp_to_dof = Vector{Vi}(undef,nnodes) m = zero(MVector{ncomps,Int}) for node in 1:nnodes for comp in 1:ncomps @@ -98,22 +105,22 @@ function return_cache(b::LagrangianDofBasis,field) end function _lagr_dof_cache(node_comp_to_val::AbstractVector,ndofs) - T = eltype(node_comp_to_val) - r = zeros(eltype(T),ndofs) + V = eltype(node_comp_to_val) + r = zeros(eltype(V),ndofs) end function _lagr_dof_cache(node_pdof_comp_to_val::AbstractMatrix,ndofs) _, npdofs = size(node_pdof_comp_to_val) - T = eltype(node_pdof_comp_to_val) - r = zeros(eltype(T),ndofs,npdofs) + V = eltype(node_pdof_comp_to_val) + r = zeros(eltype(V),ndofs,npdofs) end function evaluate!(cache,b::LagrangianDofBasis,field) c, cf = cache vals = evaluate!(cf,field,b.nodes) ndofs = length(b.dof_to_node) - T = eltype(vals) - ncomps = num_indep_components(T) + V = eltype(vals) + ncomps = num_indep_components(V) @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. diff --git a/src/ReferenceFEs/LagrangianRefFEs.jl b/src/ReferenceFEs/LagrangianRefFEs.jl index 0c6b738ba..1bf018be0 100644 --- a/src/ReferenceFEs/LagrangianRefFEs.jl +++ b/src/ReferenceFEs/LagrangianRefFEs.jl @@ -2,32 +2,49 @@ """ abstract type LagrangianRefFE{D} <: ReferenceFE{D} -Abstract type representing a Lagrangian reference FE. Lagrangian in the sense that -`get_dof_basis` returns an instance of `LagrangianDofBasis`. -The interface for this type is defined with the methods of `ReferenceFE` -plus the following ones +Abstract type representing a Lagrangian reference FE. Lagrangian in the sense +that [`get_dof_basis`](@ref) returns a [`LagrangianDofBasis`](@ref). -- [`get_face_own_nodes(reffe::LagrangianRefFE,conf::Conformity)`](@ref) -- [`get_face_own_nodes_permutations(reffe::LagrangianRefFE,conf::Conformity)`](@ref) +Implements [`ReferenceFE`](@ref)'s interface plus the following ones + +- [`get_face_own_nodes(reffe::LagrangianRefFE, conf::Conformity)`](@ref) +- [`get_face_own_nodes_permutations(reffe::LagrangianRefFE, conf::Conformity)`](@ref) - [`get_face_nodes(reffe::LagrangianRefFE)`](@ref) """ abstract type LagrangianRefFE{D} <: ReferenceFE{D} end +""" + struct Lagrangian <: ReferenceFEName +""" struct Lagrangian <: ReferenceFEName end +""" + const lagrangian = Lagrangian() + +Singleton of the [`Lagrangian`](@ref) reference FE name. +""" const lagrangian = Lagrangian() """ - get_face_own_nodes(reffe::LagrangianRefFE,conf::Conformity) + get_face_own_nodes(reffe::LagrangianRefFE[, conf::Conformity][, d::Int]) + +Return a vector containing, for each face of `reffe`'s polytope, the indices of +the nodes that belong to the interior of the face. This determines which DoFs +will be glued together in the global FE space because every DoF is placed at a node. + +Node ownership to faces depends on the [`Conformity`](@ref) in the same way than +DoF ownership does, c.f. [`get_face_own_dofs(::ReferenceFE, ...)`](@ref +get_face_own_dofs). + +If `conf` is given, the ownership is computed for `conf` and not for `reffe`'s +conformity. If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `reffe`'s polytope. """ function get_face_own_nodes(reffe::LagrangianRefFE,conf::Conformity) @abstractmethod end -""" - get_face_own_nodes(reffe::LagrangianRefFE) -""" function get_face_own_nodes(reffe::LagrangianRefFE) conf = Conformity(reffe) get_face_own_nodes(reffe,conf) @@ -45,16 +62,18 @@ function _get_face_own_nodes_l2(reffe::LagrangianRefFE) end """ - get_face_own_nodes_permutations(reffe::LagrangianRefFE,conf::Conformity) + get_face_own_nodes_permutations(reffe::LagrangianRefFE[, conf::Conformity][, d::Integer]) + +Like [`get_face_own_nodes_permutations`](@ref), but the indices are that of the +nodes instead of that of the DoFs. They are different for vector/tensor-valued +elements, for which several DoFs are placed at the same node (one per +inpedendent component). """ function get_face_own_nodes_permutations(reffe::LagrangianRefFE,conf::Conformity) face_own_nodes = get_face_own_nodes(reffe,conf) _trivial_face_own_dofs_permutations(face_own_nodes) end -""" - get_face_own_nodes_permutations(reffe::LagrangianRefFE) -""" function get_face_own_nodes_permutations(reffe::LagrangianRefFE) conf = Conformity(reffe) get_face_own_nodes_permutations(reffe,conf) @@ -62,6 +81,14 @@ end """ get_face_nodes(reffe::LagrangianRefFE) + get_face_nodes(reffe::LagrangianRefFE, d::Integer) + +Returns a vector of vector that, for each face of `reffe`'s polytope, stores the +nodes ids in the closure of the face. The difference with [`get_face_own_nodes`](@ref) +is that this includes nodes owned by the boundary faces of each face. + +If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `reffe`'s polytope. """ function get_face_nodes(reffe::LagrangianRefFE) @abstractmethod @@ -99,19 +126,28 @@ end """ get_node_coordinates(reffe::LagrangianRefFE) + +Get the vector of unique coordinate vectors of each node of `reffe`'s DoFs. """ function get_node_coordinates(reffe::LagrangianRefFE) dofs = get_dof_basis(reffe) + if dofs isa ReferenceFEs.LinearCombinationDofVector + return dofs.predofs.nodes + end dofs.nodes end """ num_nodes(reffe::LagrangianRefFE) + +Return the number of unique nodes at which `reffe`'s DoFs are placed. """ num_nodes(reffe::LagrangianRefFE) = length(get_node_coordinates(reffe)) """ get_node_and_comp_to_dof(reffe::LagrangianRefFE) + +Delegated to `reffe`'s DoFs, see [`LagrangianDofBasis`](@ref). """ function get_node_and_comp_to_dof(reffe::LagrangianRefFE) dofs = get_dof_basis(reffe) @@ -120,6 +156,8 @@ end """ get_dof_to_node(reffe::LagrangianRefFE) + +Delegated to `reffe`'s DoFs, see [`LagrangianDofBasis`](@ref). """ function get_dof_to_node(reffe::LagrangianRefFE) dofs = get_dof_basis(reffe) @@ -128,6 +166,8 @@ end """ get_dof_to_comp(reffe::LagrangianRefFE) + +Delegated to `reffe`'s DoFs, see [`LagrangianDofBasis`](@ref). """ function get_dof_to_comp(reffe::LagrangianRefFE) dofs = get_dof_basis(reffe) @@ -135,77 +175,69 @@ function get_dof_to_comp(reffe::LagrangianRefFE) end """ - get_own_nodes_permutations(reffe::LagrangianRefFE,conf::Conformity) + get_own_nodes_permutations(reffe::LagrangianRefFE) + get_own_nodes_permutations(reffe::LagrangianRefFE, conf::Conformity) + +Like [`get_face_own_nodes_permutations`](@ref), but only return the last +permutations vector, that of the nodes owned by the cell (but not its boundary faces). """ function get_own_nodes_permutations(reffe::LagrangianRefFE,conf::Conformity) - n = num_faces(reffe) - get_face_own_nodes_permutations(reffe,conf)[n] + last(get_face_own_nodes_permutations(reffe,conf)) end -""" - get_own_nodes_permutations(reffe::LagrangianRefFE) -""" function get_own_nodes_permutations(reffe::LagrangianRefFE) conf = Conformity(reffe) get_own_nodes_permutations(reffe,conf) end """ - get_vertex_node(reffe::LagrangianRefFE,conf::Conformity) -> Vector{Int} + get_vertex_node(reffe::LagrangianRefFE) -> Vector{Int} + get_vertex_node(reffe::LagrangianRefFE, conf::Conformity) -> Vector{Int} + +Return a vector containing, for each vertex of `reffe`'s polytope, the index of +the node at this vertex. An error is thrown if a vertex does not own any node +(e.g. for L2Conformity or order zero element). + +If `conf` is given, the node ownership is computed for `conf` and not for +`reffe`'s conformity. """ function get_vertex_node(reffe::LagrangianRefFE,conf::Conformity) d = 0 p = get_polytope(reffe) range = get_dimranges(p)[d+1] vertex_to_nodes = get_face_own_nodes(reffe,conf)[range] + msg = "Not all vertices own a node for the given $reffe and conformity $conf" + @check all(map(v -> !isempty(v), vertex_to_nodes)) msg map(first, vertex_to_nodes) end -""" - get_vertex_node(reffe::LagrangianRefFE) -> Vector{Int} -""" function get_vertex_node(reffe::LagrangianRefFE) conf = Conformity(reffe) get_vertex_node(reffe,conf) end -""" - get_face_own_nodes(reffe::LagrangianRefFE,conf::Conformity,d::Integer) -""" function get_face_own_nodes(reffe::LagrangianRefFE,conf::Conformity,d::Integer) p = get_polytope(reffe) range = get_dimrange(p,d) get_face_own_nodes(reffe,conf)[range] end -""" - get_face_own_nodes(reffe::LagrangianRefFE,d::Integer) -""" function get_face_own_nodes(reffe::LagrangianRefFE,d::Integer) conf = Conformity(reffe) get_face_own_nodes(reffe,conf,d) end -""" - get_face_own_nodes_permutations(reffe::LagrangianRefFE,conf::Conformity,d::Integer) -""" function get_face_own_nodes_permutations(reffe::LagrangianRefFE,conf::Conformity,d::Integer) p = get_polytope(reffe) range = get_dimrange(p,d) get_face_own_nodes_permutations(reffe,conf)[range] end -""" - get_face_own_nodes_permutations(reffe::LagrangianRefFE,d::Integer) -""" function get_face_own_nodes_permutations(reffe::LagrangianRefFE,d::Integer) conf = Conformity(reffe) get_face_own_nodes_permutations(reffe,conf,d) end -""" - get_face_nodes(reffe::LagrangianRefFE,d::Integer) -""" function get_face_nodes(reffe::LagrangianRefFE,d::Integer) p = get_polytope(reffe) range = get_dimrange(p,d) @@ -213,9 +245,9 @@ function get_face_nodes(reffe::LagrangianRefFE,d::Integer) end function get_face_own_dofs(reffe::LagrangianRefFE,conf::Conformity) - dofs = get_dof_basis(reffe) face_own_nodes = get_face_own_nodes(reffe,conf) - face_own_dofs = _generate_face_own_dofs(face_own_nodes, dofs.node_and_comp_to_dof) + node_and_comp_to_dof = get_node_and_comp_to_dof(reffe) + face_own_dofs = _generate_face_own_dofs(face_own_nodes, node_and_comp_to_dof) face_own_dofs end @@ -241,12 +273,12 @@ function _generate_face_own_dofs(face_own_nodes, node_and_comp_to_dof) end function get_face_own_dofs_permutations(reffe::LagrangianRefFE,conf::Conformity) - dofs = get_dof_basis(reffe) face_own_nodes_permutations = get_face_own_nodes_permutations(reffe,conf) + node_and_comp_to_dof = get_node_and_comp_to_dof(reffe) face_own_nodes = get_face_own_nodes(reffe,conf) face_own_dofs = get_face_own_dofs(reffe,conf) face_own_dofs_permutations = _generate_face_own_dofs_permutations( - face_own_nodes_permutations, dofs.node_and_comp_to_dof, face_own_nodes, face_own_dofs) + face_own_nodes_permutations, node_and_comp_to_dof, face_own_nodes, face_own_dofs) face_own_dofs_permutations end @@ -308,9 +340,16 @@ function (==)(a::LagrangianRefFE{D}, b::LagrangianRefFE{D}) where D xa = get_node_coordinates(a) xb = get_node_coordinates(b) t = t && (xa ≈ xb) - expsa = get_exponents(get_prebasis(a)) - expsb = get_exponents(get_prebasis(b)) - t = t && (expsa == expsb) + basisa = get_prebasis(a) + basisb = get_prebasis(b) + if basisa isa MonomialBasis + !(basisb isa MonomialBasis) && return false + expsa = get_exponents(basisa) + expsb = get_exponents(basisb) + t = t && (expsa == expsb) + else + t = t && (basisa == basisb) + end facedofsa = get_face_dofs(a) facedofsb = get_face_dofs(b) t = t && (facedofsa == facedofsb) @@ -325,14 +364,14 @@ function (==)(a::LagrangianRefFE, b::LagrangianRefFE) end """ - get_order(reffe::LagrangianRefFE) + get_order(reffe::LagrangianRefFE) = get_order(get_prebasis(reffe)) """ function get_order(reffe::LagrangianRefFE) get_order(get_prebasis(reffe)) end """ - get_orders(reffe::LagrangianRefFE) + get_orders(reffe::LagrangianRefFE) = get_orders(get_prebasis(reffe)) """ function get_orders(reffe::LagrangianRefFE) get_orders(get_prebasis(reffe)) @@ -340,10 +379,11 @@ end # Generic implementation """ - struct GenericLagrangianRefFE{C,D} <: LagrangianRefFE{D} - reffe::GenericRefFE{C,D} - face_nodes::Vector{Vector{Int}} - end + + struct GenericLagrangianRefFE{C,D} <: LagrangianRefFE{D} + reffe::GenericRefFE{C,D} + face_nodes::Vector{Vector{Int}} + end """ struct GenericLagrangianRefFE{C,D} <: LagrangianRefFE{D} reffe::GenericRefFE{C,D} @@ -356,6 +396,8 @@ get_face_nodes(reffe::GenericLagrangianRefFE) = reffe.face_nodes # Reffe +get_name(::Type{<:GenericLagrangianRefFE}) = lagrangian + num_dofs(reffe::GenericLagrangianRefFE) = num_dofs(reffe.reffe) get_polytope(reffe::GenericLagrangianRefFE) = get_polytope(reffe.reffe) diff --git a/src/ReferenceFEs/LinearCombinationDofVectors.jl b/src/ReferenceFEs/LinearCombinationDofVectors.jl deleted file mode 100644 index 507a34724..000000000 --- a/src/ReferenceFEs/LinearCombinationDofVectors.jl +++ /dev/null @@ -1,46 +0,0 @@ -""" - 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/MTWRefFEs.jl b/src/ReferenceFEs/MTWRefFEs.jl new file mode 100644 index 000000000..8529bb5f4 --- /dev/null +++ b/src/ReferenceFEs/MTWRefFEs.jl @@ -0,0 +1,70 @@ + +struct MardalTaiWinther <: ReferenceFEName end + +const mtw = MardalTaiWinther() + +Pushforward(::Type{MardalTaiWinther}) = ContraVariantPiolaMap() + +""" + struct MardalTaiWinther <: ReferenceFEName end + MardalTaiWintherRefFE(::Type{et},p::Polytope,order::Integer) where et + +Mardal-Tai-Winther reference finite element. + +References: + +- `A Robust Finite Element Method for Darcy-Stokes Flow`, Mardal, Tai and Winther (2002) +- `Transformations for Piola-mapped elements`, Aznaran, Farrell and Kirby (2022) + +""" +function MardalTaiWintherRefFE(::Type{T},p::Polytope,order::Integer) where T + D = num_dims(p) + @assert is_simplex(p) "MardalTaiWinther Reference FE only defined simplices" + @assert order == 3 "MardalTaiWinther Reference FE is by definition of order 3" + # TODO: We should just not allow this to be an argument + + prebasis = MonomialBasis(Val(D),VectorValue{D,T},3,Polynomials._p_filter) + eb = MonomialBasis(Val(1),T,0,Polynomials._p_filter) + fb = MonomialBasis(Val(D-1),T,1,Polynomials._p_filter) + + function emom(φ,μ,ds) # Edge moment function: σ_K(φ,μ) = ∫((φ⋅t)*μ)dK + t = get_edge_tangent(ds) + φt = Broadcasting(Operation(⋅))(φ,t) + Broadcasting(Operation(*))(φt,μ) + end + function fmom(φ,μ,ds) # Face moment function : σ_F(φ,μ) = ∫((φ·n)*μ)dF + n = get_facet_normal(ds) + φn = Broadcasting(Operation(⋅))(φ,n) + Broadcasting(Operation(*))(φn,μ) + end + + moments = [ + (get_dimrange(p,1),emom,eb), # Edge moments + (get_dimrange(p,D-1),fmom,fb), # Face moments + ] + + return MomentBasedReferenceFE(mtw ,p,prebasis,moments,DivConformity()) +end + +function ReferenceFE(p::Polytope,::MardalTaiWinther,::Type{T}, order) where T + MardalTaiWintherRefFE(T,p,order) +end + +function Conformity(reffe::GenericRefFE{MardalTaiWinther},sym::Symbol) + hdiv = (:Hdiv,:HDiv) + if sym == :L2 + L2Conformity() + elseif sym in hdiv + DivConformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a MardalTaiWinther reference FE. + + Possible values of conformity for this reference fe are $((:L2, hdiv...)). + """ + end +end + +function get_face_own_dofs(reffe::GenericRefFE{MardalTaiWinther}, conf::DivConformity) + get_face_dofs(reffe) +end diff --git a/src/ReferenceFEs/ModalC0RefFEs.jl b/src/ReferenceFEs/ModalC0RefFEs.jl index c6529906c..f1e7de689 100644 --- a/src/ReferenceFEs/ModalC0RefFEs.jl +++ b/src/ReferenceFEs/ModalC0RefFEs.jl @@ -1,15 +1,31 @@ +""" + struct ModalC0 <: ReferenceFEName +""" struct ModalC0 <: ReferenceFEName end +""" + const modalC0 = ModalC0() + +Singleton of the [`ModalC0`](@ref) reference FE name. +""" const modalC0 = ModalC0() """ - ModalC0RefFE(::Type{T},p::Polytope{D},orders) where {T,D} + ModalC0RefFE(::Type{T}, p::Polytope{D}, orders) 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 +This is a nodal element that is a variant of the `Lagrangian` FE on n-cubes, +with shape-functions that are scaled depending physical information. +For more details about the shape functions, see + +Badia, S.; Neiva, E. & Verdugo, F.; (2022); +Robust high-order unfitted finite elements by interpolation-based discrete extension; +https://doi.org/10.1016/j.camwa.2022.09.027 + +and Section 1.1.5 in Ern, A., & Guermond, J. L. (2013). Theory and practice of finite elements (Vol. 159). Springer Science & Business Media. @@ -135,6 +151,10 @@ function compute_shapefun_bboxes!( end end +""" + compute_cell_to_modalC0_reffe(p::Polytope{D}, ncells::Int, ::Type{T}, orders[, bbox]; + space::Symbol=_default_space(p)) +""" function compute_cell_to_modalC0_reffe( p::Polytope{D}, ncells::Int, @@ -150,7 +170,7 @@ function compute_cell_to_modalC0_reffe( 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 + filter = space == :Q ? _q_filter : _ser_filter sh(bbs) = begin a = fill(Point{D,eltype(T)}(tfill(zero(eltype(T)),Val{D}())),ndofs) @@ -181,7 +201,7 @@ function compute_cell_to_modalC0_reffe( @notimplementedif ! is_n_cube(p) @notimplementedif minimum(orders) < one(eltype(orders)) - filter = space == :Q ? _q_filter : _s_filter_mc0 + filter = space == :Q ? _q_filter : _ser_filter ndofs, predofs, lag_reffe, face_dofs = compute_reffe_data(T,p,orders,space=space) reffe = GenericRefFE{ModalC0}(ndofs, @@ -193,4 +213,4 @@ function compute_cell_to_modalC0_reffe( ModalC0Basis{D}(T,orders,filter=filter)) Fill(reffe,ncells) -end \ No newline at end of file +end diff --git a/src/ReferenceFEs/ModalScalarRefFEs.jl b/src/ReferenceFEs/ModalScalarRefFEs.jl new file mode 100644 index 000000000..9a21e4236 --- /dev/null +++ b/src/ReferenceFEs/ModalScalarRefFEs.jl @@ -0,0 +1,128 @@ +# moment based 0-form FEs + +""" + struct ModalScalar{Name} <: ReferenceFEName + +where `Name` is either `Lagrangian` or `Serendipity`. +""" +struct ModalScalar{F} <: ReferenceFEName + ModalScalar(::Lagrangian) = new{Lagrangian}() + ModalScalar(::Serendipity) = new{Serendipity}() +end + +""" + const modal_lagrangian = ModalScalar(lagrangian) + const modal_serendipity = ModalScalar(serendipity) + +Singletons of the [`ModalScalar`](@ref) reference FE name. +""" +const modal_lagrangian = ModalScalar(lagrangian) +const modal_serendipity = ModalScalar(serendipity) + +""" + ModalScalarRefFE(::Type{T}, p::Polytope{D}, order::Integer; F::Symbol, kwargs...) + +Scalar moment-based reference FE, for space FᵣΛ⁰ where `r=order` and `F` is either +`:P⁻`, `:P`, `:Q⁻` or `:S`. `T` is the type of scalar components. + +This is a variant of `lagrangian`/`serendipity` elements that is more accurate for high order (≥5). + +The `kwargs` are [`change_dof`](@ref "`change_dof` keyword argument"), +[`poly_type`](@ref "`poly_type` keyword argument") and +[`mom_poly_type`](@ref "`mom_poly_type` keyword argument"). + +For `F=:S`, `mom_poly_type` is changed for `Legendre` when `ModalC0` (the +default) is given because the moment basis need be hierarchical. +""" +function ModalScalarRefFE(::Type{T}, p::Polytope{D}, r::Integer; F::Symbol, + change_dof=true, poly_type=_mom_reffe_default_PT(p), mom_poly_type=poly_type) where {T,D} + + PT, MPT = poly_type, mom_poly_type + cart_prod = T <: MultiValue + + # TODO fix Q⁻/S on SEGMENT + if is_simplex(p) + if F in (:P⁻,:P) # same for 0 forms + prebasis = FEEC_poly_basis(Val(D),T,r, 0,:P⁻,PT; cart_prod) # P⁻ᵣΛ⁰(□ᴰ) + mb = [ (r-d-1 >= 0 ? + FEEC_poly_basis(Val(d),T,r-d-1,d,:P, MPT; cart_prod) + : nothing) for d in 0:D ] # PᵨΛᵈ(□ᵈ), ρ = r-d-1 + #elseif F==:P + # prebasis = FEEC_poly_basis(Val(D),T,r, 0,:P, PT; cart_prod) # PᵣΛ⁰(□ᴰ) + # mb = [ (r-d > 0 ? + # FEEC_poly_basis(Val(d),T,r-d,d,:P⁻,MPT; cart_prod) + # : nothing) for d in 0:D ] # P⁻ᵨΛᵈ(□ᵈ), ρ = r-d + else + @notimplemented "Only :P⁻ and :P elements are implemented on simplices, got F=$F." + end + elseif is_n_cube(p) + if F==:Q⁻ + prebasis = FEEC_poly_basis(Val(D),T,r, 0,:Q⁻,PT; cart_prod) # Q⁻ᵣΛ⁰(□ᴰ) + mb = [ ((r-1 > 0 || d==0) ? + FEEC_poly_basis(Val(d),T,r-1,d,:Q⁻,MPT; cart_prod) + : nothing) for d in 0:D ] # Q⁻ᵨΛᵈ(□ᵈ), ρ = r-1 + elseif F==:S + prebasis = FEEC_poly_basis(Val(D),T,r, 0,:S,PT; cart_prod) # SᵣΛ⁰(□ᴰ) + MPT = MPT == Polynomials.ModalC0 ? Legendre : MPT + mb = [ (r-2d >= 0 ? + FEEC_poly_basis(Val(d),T,r-2d,d,:P,MPT; cart_prod) + : nothing) for d in 0:D ] # PᵨΛᵈ(□ᵈ), ρ = r-2*d + else + @notimplemented "Only :Q⁻ and :S elements are implemented on n-cubes, got F=$F." + end + else + @notimplemented "ModalScalar FE is only implemented on simplices and n-cubes." + end + + # The vector proxies of d-forms that we get in `mb` must be turned back into + # volume forms by multiplying by |df| + mom_integrand = if is_simplex(p) + @check D<4 # because _get_dfaces_measures is limited to 3D + function mom_s(φ,μ,ds) # moment function: σ_K(φ,μ) = ∫(φ*μ)df + d = num_dims(ds.fpoly) + face_measure = _get_dfaces_measure(ds.cpoly, d) + df = Gridap.Fields.ConstantField(face_measure[ds.face]) + dμ = Broadcasting(Operation(/))(μ,df) + Broadcasting(Operation(⊙))(φ,dμ) # using inner in case of cartesian product space + end + elseif is_n_cube(p) + # TODO this assumes p is a reference n-cube (all d-faces have same d-volume), + # otherwise face volume needed + @assert p isa ExtrusionPolytope + function mom_c(φ,μ,ds) # moment function: σ_K(φ,μ) = ∫(φ*μ)df + Broadcasting(Operation(⊙))(φ,μ) # using inner in case of cartesian product space + end + end + + moments = Tuple[ (get_dimrange(p,d), mom_integrand, mb[d+1]) for d in 0:D if !isnothing(mb[d+1]) ] + + name = F == :S ? modal_serendipity : modal_lagrangian + conf = GradConformity() + change_dof = _validate_change_dof(change_dof,prebasis,p,conf) + MomentBasedReferenceFE(name,p,prebasis,moments,conf; change_dof) +end + +function ReferenceFE(p::Polytope,::ModalScalar{Name},::Type{T}, order; kwargs...) where {Name, T} + F = Name == Serendipity ? :S : (is_n_cube(p) ? :Q⁻ : :P) + ModalScalarRefFE(T,p,order; F, kwargs...) +end + +function Conformity(::GenericRefFE{<:ModalScalar},sym::Symbol) + hgrad = (:H1,:C0,:Hgrad,:HGrad) + if sym == :L2 + L2Conformity() + elseif sym in hgrad + GradConformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a Nedelec reference FE. + + Possible values of conformity for this reference fe are $((:L2, hcurl...)). + """ + end +end + +function get_face_own_dofs(reffe::GenericRefFE{<:ModalScalar}, conf::GradConformity) + get_face_dofs(reffe) +end +#get_face_dofs(::GenericRefFE{<:ModalScalar}, ::GradConformity) = throw(ErrorException("not implemeted")) diff --git a/src/ReferenceFEs/MomentBasedReferenceFEs.jl b/src/ReferenceFEs/MomentBasedReferenceFEs.jl new file mode 100644 index 000000000..3f412aa00 --- /dev/null +++ b/src/ReferenceFEs/MomentBasedReferenceFEs.jl @@ -0,0 +1,484 @@ + +# MomentBasedDofBasis + +struct Moment <: Dof end + +""" + struct MomentBasedDofBasis{P,V} <: AbstractVector{Moment} + +Implementation of a basis of discretized moment DoFs, where `P` is the type of +the quadrature nodes, and `V` the value type of the shape functions. +""" +struct MomentBasedDofBasis{P,V} <: AbstractVector{Moment} + nodes::Vector{P} + face_moments::Vector{Array{V}} + face_nodes::Vector{UnitRange{Int}} + face_own_moms::Vector{Vector{Int}} + + function MomentBasedDofBasis(nodes,f_moments,f_nodes,f_own_moms) + P = eltype(nodes) + V = eltype(eltype(f_moments)) + new{P,V}(nodes,f_moments,f_nodes,f_own_moms) + end + + # Unused and untested + #function MomentBasedDofBasis(f_nodes,f_moments,f_own_moms) + # P = eltype(eltype(f_nodes)) + # V = eltype(eltype(f_moments)) + # nodes = P[] + # face_nodes = UnitRange{Int}[] + # nfaces = length(f_nodes) + # n = 1 + # for fi in 1:nfaces + # nodes_fi = f_nodes[fi] + # nini = n + # for node_fi in nodes_fi + # push!(nodes,node_fi) + # n += 1 + # end + # nend = n-1 + # push!(face_nodes,nini:nend) + # end + # new{P,V}(nodes,f_moments,face_nodes,f_own_moms) + #end +end + +Base.size(a::MomentBasedDofBasis) = (num_dofs(a),) +Base.axes(a::MomentBasedDofBasis) = (Base.OneTo(num_dofs(a)),) +Base.getindex(a::MomentBasedDofBasis,i::Integer) = Moment() +Base.IndexStyle(::MomentBasedDofBasis) = IndexLinear() + +""" + get_nodes(b::MomentBasedDofBasis) + +Get the vector of DoF quadrature nodes of `b`. +""" +get_nodes(b::MomentBasedDofBasis) = b.nodes + +""" + get_face_moments(b::MomentBasedDofBasis) + +Return the vector of discretized moments for each face of the underlying polytope. +""" +get_face_moments(b::MomentBasedDofBasis) = b.face_moments + +""" + get_face_own_dofs(b::MomentBasedDofBasis) + +The ownership of `b`'s dofs to faces of the underlying polytope is defined as +the face the moment was defined on. +""" +get_face_own_dofs(b::MomentBasedDofBasis) = b.face_own_moms + +""" + get_face_nodes_dofs(b::MomentBasedDofBasis) + +Return the moment quadrature node indices on each face of the underlying polytope. +""" +get_face_nodes_dofs(b::MomentBasedDofBasis) = b.face_nodes + +function num_dofs(b::MomentBasedDofBasis) + n = 0 + for m in b.face_moments + n += size(m,2) + end + n +end + +function return_cache(b::MomentBasedDofBasis{P,V}, field) where {P,V} + cf = return_cache(field,b.nodes) + vals = evaluate!(cf,field,b.nodes) + Vr = eltype(vals) + T = typeof( zero(V) ⊙ zero(Vr) ) + r = Array{T}(undef, (num_dofs(b), size(field)...)) + c = CachedArray(r) + return c, cf +end + +function evaluate!(cache, b::MomentBasedDofBasis, field::Field) + c, cf = cache + setsize!(c, size(b)) + vals = evaluate!(cf,field,b.nodes) + dofs = c.array + + o = 1 + z = zero(eltype(dofs)) + face_nodes = b.face_nodes + face_moments = b.face_moments + for face in eachindex(face_moments) + moments = face_moments[face] + if !iszero(length(moments)) + nodes = face_nodes[face] + ni,nj = size(moments) + for j in 1:nj + dofs[o] = z + for i in 1:ni + dofs[o] += moments[i,j] ⊙ vals[nodes[i]] + end + o += 1 + end + end + end + + return dofs +end + +function evaluate!(cache, b::MomentBasedDofBasis, field::AbstractVector{<:Field}) + c, cf = cache + setsize!(c, (size(b,1),length(field))) + vals = evaluate!(cf,field,b.nodes) + dofs = c.array + + o = 1 + na = size(vals,2) + z = zero(eltype(dofs)) + face_nodes = b.face_nodes + face_moments = b.face_moments + for face in eachindex(face_moments) + moments = face_moments[face] + if !iszero(length(moments)) + nodes = face_nodes[face] + ni,nj = size(moments) + for j in 1:nj + for a in 1:na + dofs[o,a] = z + for i in 1:ni + dofs[o,a] += moments[i,j] ⊙ vals[nodes[i],a] + end + end + o += 1 + end + end + end + + return dofs +end + +""" + MomentBasedDofBasis(p::Polytope, prebasis::AbstractVector{<:Field}, moments) + +Creates a basis of DoFs defined by moments on faces of `p`. + +`moments` is a vector of moment descriptors, each one is given by a triplet +(f,σ,μ) where + - f is collection of ids of faces Fₖ of `p`, that index `get_faces(p)`, + - σ is a function σ(φ,μ,ds) **linear** in φ and μ that takes two Field-vectors φ and μ and a `FaceMeasure` ds and returns a Field-like object to be integrated over each face Fₖ, + - μ is a polynomials basis on Fₖ. + +The moment DoFs are thus defined by φ -> ∫_Fₖ σ(φ,μᵢ,ds)dFₖ, ∀ σ,k,i. +In the final basis, DoFs are ordered by moment, then by face, then by "test" polynomial. + +All the faces in a moment must be of the same type (have same reference face). +""" +function MomentBasedDofBasis( + p::Polytope{D}, + prebasis::AbstractVector{<:Field}, + moments::AbstractVector{<:Tuple}, + ) where D + + n_faces = num_faces(p) + n_moments = length(moments) + face_dims = get_facedims(p) + face_offsets = get_offsets(p) + reffaces, face_types = _compute_reffaces_and_face_types(p) + + V = return_type(prebasis) + φ_vec = representatives_of_componentbasis_dual(V) + φ = map(constant_field,φ_vec) + + # Create face measures for each moment + order = get_order(prebasis) + measures = Vector{FaceMeasure}(undef,n_moments) + for (k,(faces,σ,μ)) in enumerate(moments) + ftype = face_types[first(faces)] + @check all(isequal(ftype), face_types[faces]) + qdegree = order + get_order(μ) + 1 + fp = reffaces[ftype] + measures[k] = FaceMeasure(p,fp,qdegree) + end + + # Count number of moments and quad pts per face + face_n_moms = zeros(Int,n_faces) + face_n_nodes = zeros(Int,n_faces) + for ((faces,σ,μ),ds) in zip(moments,measures) + face_n_moms[faces] .+= length(μ) + face_n_nodes[faces] .+= num_points(ds.quad) + end + + # Compute face moment and node indices + n_moms = 0 + n_nodes = 0 + face_nodes = Vector{UnitRange{Int}}(undef, n_faces) + face_moments = Vector{Array{V}}(undef, n_faces) + face_own_moms = Vector{Vector{Int}}(undef,n_faces) + for face in 1:n_faces + n_moms_i = face_n_moms[face] + n_nodes_i = face_n_nodes[face] + face_nodes[face] = (n_nodes+1):(n_nodes+n_nodes_i) + face_moments[face] = zeros(V,n_nodes_i,n_moms_i) + face_own_moms[face] = collect((n_moms+1):(n_moms+n_moms_i)) + n_moms += n_moms_i + n_nodes += n_nodes_i + end + + # Compute face moments and nodes + fill!(face_n_moms,0) + fill!(face_n_nodes,0) + nodes = Vector{Point{D,Float64}}(undef,n_nodes) + for ((faces,σ,μ),ds) in zip(moments,measures) + cache = return_cache(σ,φ,μ,ds) + + for face in faces + d = face_dims[face] + lface = face - face_offsets[d+1] + set_face!(ds,lface) + + # vals : (nN, nμ, nφ), coords : (nN) + vals, coords = evaluate!(cache,σ,φ,μ,ds) + # test_moment(σ,prebasis,μ,ds) + + mom_offset = face_n_moms[face] + node_offset = first(face_nodes[face]) + face_n_nodes[face] - 1 + for i in axes(vals,1) + for j in axes(vals,2) + face_moments[face][i,j+mom_offset] = V(vals[i,j,:]...) + end + nodes[i+node_offset] = coords[i] + end + + face_n_nodes[face] += size(vals,1) + face_n_moms[face] += size(vals,2) + end + end + + MomentBasedDofBasis(nodes, face_moments, face_nodes, face_own_moms) +end + +# Unused and untested +#function test_moment(σ,prebasis,μ,ds) +# T = return_type(prebasis) +# φ = map(constant_field,dual_component_basis_representatives(T)) +# vals, coords = evaluate(σ,φ,μ,ds) +# +# φx = evaluate(prebasis, coords) # (nN, nφ) +# σx, _ = evaluate(σ,prebasis,μ,ds) +# +# σx_bis = zeros(size(vals,1),size(vals,2),size(σx,3)) +# for i in axes(vals,1) +# for j in axes(vals,2) +# cx = T(vals[i,j,:]...) +# σx_bis[i,j,:] .= map(y -> inner(y,cx),φx[i,:]) +# end +# end +# +# println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") +# +# println(" > Values: ") +# for i in axes(vals,2) +# println(" >> Mu = ",i) +# for j in axes(vals,3) +# println(" >>> Phi -> ", sum(vals[:,i,j])) +# end +# end +# +# println(" > Moments: ") +# for i in axes(vals,1) +# display(σx[i,:,:]) +# display(σx_bis[i,:,:]) +# println("----------------------------------------") +# end +# @assert σx ≈ σx_bis +# println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%") +#end + + +############### +# FaceMeasure # +############### + +# evaluate! and return_cache arn't type stable, at least because `quad` and +# `fmaps` are not concretely typed. +# I think we could use a MVector{1,Int} to store `face` and make the +# struct non-mutable, this would likely be more performent +mutable struct FaceMeasure{Df,Dc} + face ::Int + cpoly::Polytope{Dc} + fpoly::Polytope{Df} + quad ::Quadrature + fmaps::Vector{<:Field} + function FaceMeasure( + cpoly::Polytope{Dc},fpoly::Polytope{Df},order::Int + ) where {Df,Dc} + # Quadrature on the face + quad = Quadrature(fpoly,order) + # Face to cell coordinate map + #if Df == Dc + # fmaps = [GenericField(identity)] + #else # TODO: Could this be an AffineMap? + fcoords = get_face_coordinates(cpoly,Df) + basis = get_shapefuns(LagrangianRefFE(Float64,fpoly,1)) + fmaps = map(c -> linear_combination(c,basis),fcoords) + #end + new{Df,Dc}(1,cpoly,fpoly,quad,fmaps) + end +end + +function set_face!(m::FaceMeasure{Df},face::Int) where {Df} + @assert 0 < face <= num_faces(m.cpoly, Df) + m.face = face + return m +end + +# TODO: Normals are accesed, but tangent are computed on demand. This means +# that we will be repeating work unless we cache them. +function get_facet_normal(m::FaceMeasure{Df,Dc}) where {Df,Dc} + @assert Df == Dc - 1 + n = get_facet_normal(m.cpoly) + return ConstantField(n[m.face]) +end + +function get_edge_tangent(m::FaceMeasure{1,Dc}) where {Dc} + t = get_edge_tangent(m.cpoly) + return ConstantField(t[m.face]) +end + +# Matrix of the contravariant piola map from `m.fpoly` to to the face `m.face` +# of `m.cpoly`, used to extend a Df-dimensional vector in `m.fpoly` to a +# Dc-dimensional one that lives in the tangent space of the Dc-embedded Df-dimensional +# manifold `m.face`. +function get_extension(m::FaceMeasure{Df,Dc}) where {Df,Dc} + @assert Df == Dc - 1 + vs = ReferenceFEs._nfaces_vertices(Float64,m.cpoly,Df)[m.face] + J = TensorValue(hcat([vs[2]-vs[1]...],[vs[3]-vs[1]...])) + return ConstantField(J/meas(transpose(J))) +end +# function get_extension(m::FaceMeasure{Df,Dc}) where {Df,Dc} +# @assert Df == Dc - 1 +# fmap = m.fmaps[m.face] +# J = Broadcasting(∇)(fmap) +# return Operation(*)(Operation(transpose)(J),Operation(x -> 1/meas(x))(J)) +# end + +function Arrays.return_cache( + σ::Function, # σ(φ,μ,ds) -> Field/Array{Field} + φ::AbstractArray{<:Field}, # φ: prebasis (defined on the cell) + μ::AbstractArray{<:Field}, # μ: polynomial basis (defined on the face) + ds::FaceMeasure # ds: face measure +) + fmap = ds.fmaps[ds.face] + φf = transpose(Broadcasting(Operation(∘))(φ,fmap)) + return_cache(σ(φf,μ,ds),ds) +end + +function Arrays.evaluate!( + cache, + σ::Function, # σ(φ,μ,ds) -> Field/Array{Field} + φ::AbstractArray{<:Field}, # φ: prebasis (defined on the cell) + μ::AbstractArray{<:Field}, # μ: polynomial basis (defined on the face) + ds::FaceMeasure # ds: face measure +) + fmap = ds.fmaps[ds.face] + φf = transpose(Broadcasting(Operation(∘))(φ,fmap)) + evaluate!(cache,σ(φf,μ,ds),ds) +end + +function Arrays.return_cache(f,ds::FaceMeasure) + fmap = ds.fmaps[ds.face] + + xf = get_coordinates(ds.quad) + w = get_weights(ds.quad) + fmap_cache = return_cache(fmap,xf) + + detJ = Broadcasting(Operation(meas))(Broadcasting(∇)(fmap)) + detJ_cache = return_cache(detJ,xf) + + f_cache = return_cache(f,xf) + return fmap_cache, detJ_cache, f_cache, xf, w +end + +function Arrays.evaluate!(cache,f,ds::FaceMeasure) + fmap_cache, detJ_cache, f_cache, xf, w = cache + fmap = ds.fmaps[ds.face] + + detJ = Broadcasting(Operation(meas))(Broadcasting(∇)(fmap)) + dF = evaluate!(detJ_cache,detJ,xf) + + xc = evaluate!(fmap_cache,fmap,xf) # quad pts on the cell + fx = evaluate!(f_cache,f,xf) # f evaluated on the quad pts + fx .= (w .* dF) .* fx + return fx, xc +end + + +########################## +# MomentBasedReferenceFE # +########################## + +""" + MomentBasedReferenceFE( + name::ReferenceFEName, + p::Polytope, + prebasis::AbstractVector{<:Field}, + moments::AbstractVector{<:Tuple}, + conformity::Conformity; + change_dof=false + ) + +Constructs a ReferenceFEs on `p` with a moment DoF (pre-)basis defined by +`moments`. See [`MomentBasedDofBasis`](@ref) for the specification of `moments`. + +If `change_dof=true`, `prebasis` is used as shape functions. +This requires it to fullfill a geometric decomposition relative to the faces of +`p` for `conformity`, see the [*Geometric decompositions*](@ref "Geometric decompositions") +section in the docs. + +Warning, this function does not check that the moments are properly defined to implement +the given `conformity`. It is assumed that if ``σ(φ) = 0`` for a moment ``σ`` +owned by a face ``f`` of `p`, then the `conformity`-trace of ``φ`` over ``f`` is zero. +""" +function MomentBasedReferenceFE( + name::ReferenceFEName, + p::Polytope, + prebasis::AbstractVector{<:Field}, + moments::AbstractVector{<:Tuple}, + conformity::Conformity; + change_dof=false +) + + dof_basis = MomentBasedDofBasis(p, prebasis, moments) + n_dofs = length(dof_basis) + metadata = nothing + + if change_dof + # Use geometric decomposition to avoid shapefuns change of basis. + # The dofs are computed as dual to the shapefuns, using a change of basis + # from the moment basis + + @assert has_geometric_decomposition(prebasis, p, conformity) + shapefuns, predofs = prebasis, dof_basis + + if conformity isa DivConformity + sign_flip = get_facet_flux_sign_flip(shapefuns, p, conformity) + shapefuns = linear_combination(sign_flip,shapefuns) + end + + dofs = compute_dofs(predofs, shapefuns) + face_own_dofs = get_face_own_funs(prebasis, p, conformity) + return GenericRefFE{typeof(name)}( + n_dofs, p, predofs, conformity, metadata, face_own_dofs, shapefuns, dofs + ) + end # else, standard prebasis inversion + + face_own_dofs = get_face_own_dofs(dof_basis) + GenericRefFE{typeof(name)}( + n_dofs, p, prebasis, dof_basis, conformity, metadata, face_own_dofs + ) +end + +# Default polynomial type for moment based reference FEs +function _mom_reffe_default_PT(p) + is_simplex(p) && return Bernstein + is_n_cube(p) && return Polynomials.ModalC0 + Monomial +end + diff --git a/src/ReferenceFEs/NedelecRefFEs.jl b/src/ReferenceFEs/NedelecRefFEs.jl index 52072d68a..0b858f4d5 100644 --- a/src/ReferenceFEs/NedelecRefFEs.jl +++ b/src/ReferenceFEs/NedelecRefFEs.jl @@ -1,61 +1,112 @@ -struct CurlConformity <: Conformity end - -struct Nedelec <: ReferenceFEName end - -const nedelec = Nedelec() +""" + struct Nedelec{kind} <: ReferenceFEName +""" +struct Nedelec{kind} <: ReferenceFEName + Nedelec{1}() = new{1}() + Nedelec{2}() = new{2}() +end """ - NedelecRefFE(::Type{et},p::Polytope,order::Integer) where et + const nedelec = Nedelec{1}() + const nedelec1 = nedelec -The `order` argument has the following meaning: the curl of the functions in this basis -is in the Q space of degree `order`. +Singleton of the first kind of [`Nedelec`](@ref) reference FE name. """ -function NedelecRefFE(::Type{et},p::Polytope,order::Integer) where et +const nedelec = Nedelec{1}() +const nedelec1 = nedelec - # @santiagobadia : Project, go to complex numbers - D = num_dims(p) +""" + const nedelec2 = Nedelec{2}() - if is_n_cube(p) - prebasis = QGradMonomialBasis{D}(et,order) - elseif is_simplex(p) - prebasis = Polynomials.NedelecPrebasisOnSimplex{D}(order) - else - @unreachable "Only implemented for n-cubes and simplices" - end +Singleton of the second kind of [`Nedelec`](@ref) reference FE name. +""" +const nedelec2 = Nedelec{2}() - nf_nodes, nf_moments = _Nedelec_nodes_and_moments(et,p,order) +Pushforward(::Type{<:Nedelec}) = CoVariantPiolaMap() - face_own_dofs = _face_own_dofs_from_moments(nf_moments) +""" + NedelecRefFE(::Type{T}, p::Polytope, order::Integer; kind::Int=1, kwargs...) - face_dofs = face_own_dofs +The `order` argument has the following meaning: the curl of the functions in +this basis is in the ℙ/ℚ space of degree `order`. `T` is the type of scalar components. - dof_basis = MomentBasedDofBasis(nf_nodes, nf_moments) +The `kwargs` are [`change_dof`](@ref "`change_dof` keyword argument"), +[`poly_type`](@ref "`poly_type` keyword argument") and +[`mom_poly_type`](@ref "`mom_poly_type` keyword argument"). +""" +function NedelecRefFE( + ::Type{T},p::Polytope{D},order::Integer; kind::Int=1, + change_dof=true, poly_type=_mom_reffe_default_PT(p), mom_poly_type=poly_type) where {T,D} - ndofs = num_dofs(dof_basis) + PT, MPT = poly_type, mom_poly_type + rotate_90 = D==2 - metadata = nothing + if is_n_cube(p) + @check kind == 1 "Nedelec reference elements of the second kind are only defined on simplices" + prebasis = FEEC_poly_basis(Val(D),T,order+1,1,:Q⁻,PT) # Q⁻ᵣΛ¹(□ᴰ), r = order+1 + eb = FEEC_poly_basis(Val(1),T,order,0, :Q⁻,MPT) # Edge basis Q⁻ᵨΛ⁰(□¹), ρ = r-1 + fb = order>0 ? FEEC_poly_basis(Val(2),T,order,1, :Q⁻,MPT) : nothing # Facet basis Q⁻ᵨΛ¹(□²), ρ = r-1 (only D=3) + cb = order>0 ? FEEC_poly_basis(Val(D),T,order,D-1,:Q⁻,MPT; rotate_90) : nothing # Cell basis Q⁻ᵨΛᴰ⁻¹(□ᴰ),ρ = r-1 + elseif is_simplex(p) + if kind == 1 + prebasis = FEEC_poly_basis(Val(D),T,order+1,1, :P⁻,PT) # P⁻ᵣΛ¹(△ᴰ), r = order+1 + eb = FEEC_poly_basis(Val(1),T,order,0, :P ,MPT) # Edge basis PᵨΛ⁰(△¹), ρ = r-1 + fb = order>0 ? FEEC_poly_basis(Val(2),T,order-1,1, :P ,MPT; rotate_90=true) : nothing # Facet basis PᵨΛ¹(△²), ρ = r-2 (only D=3) + cb = order>D-2 ? FEEC_poly_basis(Val(D),T,order-D+1,D-1,:P ,MPT; rotate_90) : nothing # Cell basis PᵨΛᴰ⁻¹(△ᴰ),ρ = r-D + else + @check order > 0 "the lowest order of Nedelec elements of second kind is 1" + prebasis = FEEC_poly_basis(Val(D),T,order,1, :P ,PT) # PᵣΛ¹(△ᴰ), r = order + eb = FEEC_poly_basis(Val(1),T,order,0, :P⁻,MPT) # Edge basis P⁻ᵨΛ⁰(△¹), ρ = r + fb = order>1 ? FEEC_poly_basis(Val(2),T,order-1,1, :P⁻,MPT; rotate_90=true) : nothing # Facet basis P⁻ᵨΛ¹(△²), ρ = r-1 (only D=3) + cb = order≥D ? FEEC_poly_basis(Val(D),T,order-D+1,D-1,:P⁻,MPT; rotate_90) : nothing # Cell basis P⁻ᵨΛᴰ⁻¹(△ᴰ),ρ = r-D+1 + end + else + @notimplemented "Nedelec Reference FE only implemented for n-cubes and simplices" + end - reffe = GenericRefFE{Nedelec}( - ndofs, - p, - prebasis, - dof_basis, - CurlConformity(), - metadata, - face_dofs) + function cmom(φ,μ,ds) # Cell moment function: σ_K(φ,μ) = ∫(φ⋅μ)dK + Broadcasting(Operation(⋅))(φ,μ) + end + function fmom_HEX(φ,μ,ds) # Face moment function: σ_F(φ,μ) = ∫((φ×n)⋅μ)dF + o = get_facet_orientations(ds.cpoly)[ds.face] # This is a hack to avoid a sign map + n = o*get_facet_normal(ds) + E = get_extension(ds) + Eμ = Broadcasting(Operation(⋅))(E,μ) # We have to extend the basis to 3D + φn = Broadcasting(Operation(×))(n,φ) + Broadcasting(Operation(⋅))(φn,Eμ) + end + function fmom_TET(φ,μ,ds) # Face moment function: σ_F(φ,μ) = ∫((φ×n)⋅μ)dF + E = get_extension(ds) + Eμ = Broadcasting(Operation(⋅))(E,μ) # We have to extend the basis to 3D + Broadcasting(Operation(⋅))(φ,Eμ) + end + function emom(φ,μ,ds) # Edge moment function: σ_E(φ,μ) = ∫((φ⋅t)*μ)dE + t = get_edge_tangent(ds) + φt = Broadcasting(Operation(⋅))(φ,t) + Broadcasting(Operation(*))(φt,μ) + end - reffe -end + moments = Tuple[ + (get_dimrange(p,1),emom,eb), # Edge moments + ] + if (D == 3) && order > kind-1 + fmom = ifelse(is_n_cube(p),fmom_HEX,fmom_TET) + push!(moments,(get_dimrange(p,D-1),fmom,fb)) # Face moments + end + if (is_n_cube(p) && order > 0) || (is_simplex(p) && order > D+kind-3) + push!(moments,(get_dimrange(p,D),cmom,cb)) # Cell moments + end -function ReferenceFE(p::Polytope,::Nedelec, order) - NedelecRefFE(Float64,p,order) + conf = CurlConformity() + change_dof = _validate_change_dof(change_dof, prebasis, p, conf) + return MomentBasedReferenceFE(Nedelec{kind}(),p,prebasis,moments,conf; change_dof) end -function ReferenceFE(p::Polytope,::Nedelec,::Type{T}, order) where T - NedelecRefFE(T,p,order) +function ReferenceFE(p::Polytope,::Nedelec{K},::Type{T}, order; kwargs...) where {K, T} + NedelecRefFE(T,p,order; kind=K, kwargs...) end -function Conformity(reffe::GenericRefFE{Nedelec},sym::Symbol) +function Conformity(reffe::GenericRefFE{<:Nedelec},sym::Symbol) hcurl = (:Hcurl,:HCurl) if sym == :L2 L2Conformity() @@ -70,33 +121,27 @@ function Conformity(reffe::GenericRefFE{Nedelec},sym::Symbol) end end -function get_face_own_dofs(reffe::GenericRefFE{Nedelec}, conf::CurlConformity) +function get_face_own_dofs(reffe::GenericRefFE{<:Nedelec}, ::CurlConformity) reffe.face_dofs # For Nedelec, this member variable holds the face owned dofs end -function get_face_own_dofs(reffe::GenericRefFE{Nedelec}, conf::L2Conformity) - face_own_dofs=[Int[] for i in 1:num_faces(reffe)] - face_own_dofs[end]=collect(1:num_dofs(reffe)) - face_own_dofs -end - -function get_face_dofs(reffe::GenericRefFE{Nedelec,Dc}) where Dc - face_dofs=[Int[] for i in 1:num_faces(reffe)] - face_own_dofs=get_face_own_dofs(reffe) +function get_face_dofs(reffe::GenericRefFE{<:Nedelec,Dc}) where Dc + face_dofs = [Int[] for i in 1:num_faces(reffe)] + face_own_dofs = get_face_own_dofs(reffe) p = get_polytope(reffe) - for d=1:Dc # Starting from edges, vertices do not own DoFs for Nedelec + for d = 1:Dc # Starting from edges, vertices do not own DoFs for Nedelec first_face = get_offset(p,d) nfaces = num_faces(reffe,d) - for face=first_face+1:first_face+nfaces - for df=1:d-1 + for face = first_face+1:first_face+nfaces + for df = 1:d-1 face_faces = get_faces(p,d,df) first_cface = get_offset(p,df) for cface in face_faces[face-first_face] cface_own_dofs = face_own_dofs[first_cface+cface] for dof in cface_own_dofs - push!(face_dofs[face],dof) + push!(face_dofs[face],dof) end - end + end end for dof in face_own_dofs[face] push!(face_dofs[face],dof) @@ -105,262 +150,3 @@ function get_face_dofs(reffe::GenericRefFE{Nedelec,Dc}) where Dc end face_dofs end - - -function _Nedelec_nodes_and_moments(::Type{et}, p::Polytope, order::Integer) where et - - @notimplementedif !( is_n_cube(p) || (is_simplex(p) ) ) - - D = num_dims(p) - ft = VectorValue{D,et} - pt = Point{D,et} - - nf_nodes = [ zeros(pt,0) for face in 1:num_faces(p)] - nf_moments = [ zeros(ft,0,0) for face in 1:num_faces(p)] - - ecips, emoments = _Nedelec_edge_values(p,et,order) - erange = get_dimrange(p,1) - nf_nodes[erange] = ecips - nf_moments[erange] = emoments - - if ( num_dims(p) == 3 && order > 0) - - if is_n_cube(p) - fcips, fmoments = _Nedelec_face_values(p,et,order) - else - fcips, fmoments = _Nedelec_face_values_simplex(p,et,order) - end - - frange = get_dimrange(p,D-1) - nf_nodes[frange] = fcips - nf_moments[frange] = fmoments - - end - - if ( is_n_cube(p) && order > 0) || ( is_simplex(p) && order > D-2) - - ccips, cmoments = _Nedelec_cell_values(p,et,order) - crange = get_dimrange(p,D) - nf_nodes[crange] = ccips - nf_moments[crange] = cmoments - - end - - nf_nodes, nf_moments -end - -function _Nedelec_edge_values(p,et,order) - - # Reference facet - dim1 = 1 - ep = Polytope{dim1}(p,1) - - # geomap from ref face to polytope faces - egeomap = _ref_face_to_faces_geomap(p,ep) - - # Compute integration points at all polynomial edges - degree = (order)*2 - equad = Quadrature(ep,degree) - cips = get_coordinates(equad) - wips = get_weights(equad) - - - c_eips, ecips, ewips = _nfaces_evaluation_points_weights(p, egeomap, cips, wips) - - # Edge moments, i.e., M(Ei)_{ab} = q_RE^a(xgp_REi^b) w_Fi^b t_Ei ⋅ () - eshfs = MonomialBasis(et,ep,order) - emoments = _Nedelec_edge_moments(p, eshfs, c_eips, ecips, ewips) - - return ecips, emoments - -end - -function _Nedelec_edge_moments(p, fshfs, c_fips, fcips, fwips) - ts = get_edge_tangent(p) - nc = length(c_fips) - cfshfs = fill(fshfs, nc) - cvals = lazy_map(evaluate,cfshfs,c_fips) - cvals = [fwips[i].*cvals[i] for i in 1:nc] - # @santiagobadia : Only working for oriented meshes now - cvals = [ _broadcast(typeof(t),t,b) for (t,b) in zip(ts,cvals)] - return cvals -end - -function _Nedelec_face_values(p,et,order) - - # Reference facet - @assert is_n_cube(p) "We are assuming that all n-faces of the same n-dim are the same." - fp = Polytope{num_dims(p)-1}(p,1) - - # geomap from ref face to polytope faces - fgeomap = _ref_face_to_faces_geomap(p,fp) - - # Compute integration points at all polynomial edges - degree = (order)*2 - fquad = Quadrature(fp,degree) - fips = get_coordinates(fquad) - wips = get_weights(fquad) - - c_fips, fcips, fwips = _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) - - # Face moments, i.e., M(Fi)_{ab} = w_Fi^b q_RF^a(xgp_RFi^b) (n_Fi × ()) - fshfs = QGradMonomialBasis{num_dims(fp)}(et,order-1) - - fmoments = _Nedelec_face_moments(p, fshfs, c_fips, fcips, fwips) - - return fcips, fmoments - -end - -function _Nedelec_face_moments(p, fshfs, c_fips, fcips, fwips) - nc = length(c_fips) - cfshfs = fill(fshfs, nc) - cvals = lazy_map(evaluate,cfshfs,c_fips) - - fvs = _nfaces_vertices(Float64,p,num_dims(p)-1) - fts = [hcat([vs[2]-vs[1]...],[vs[3]-vs[1]...]) for vs in fvs] - - # Ref facet FE functions evaluated at the facet integration points (in ref facet) - cvals = [fwips[i].*cvals[i] for i in 1:nc] - - fns = get_facet_normal(p) - os = get_facet_orientations(p) - # @santiagobadia : Temporary hack for making it work for structured hex meshes - ft = eltype(fns) - cvals = [ _broadcast_extend(ft,Tm,b) for (Tm,b) in zip(fts,cvals)] - cvals = [ _broadcast_cross(ft,n*o,b) for (n,o,b) in zip(fns,os,cvals)] - return cvals -end - -function _Nedelec_face_values_simplex(p,et,order) - - # Reference facet - @assert is_simplex(p) "We are assuming that all n-faces of the same n-dim are the same." - fp = Polytope{num_dims(p)-1}(p,1) - - # geomap from ref face to polytope faces - fgeomap = _ref_face_to_faces_geomap(p,fp) - - # Compute integration points at all polynomial edges - degree = (order)*2 - fquad = Quadrature(fp,degree) - fips = get_coordinates(fquad) - wips = get_weights(fquad) - - c_fips, fcips, fwips, fJtips = _nfaces_evaluation_points_weights_with_jac(p, fgeomap, fips, wips) - - Df = num_dims(fp) - fshfs = MonomialBasis{Df}(VectorValue{Df,et},order-1,(e,k)->sum(e)<=k) - - fmoments = _Nedelec_face_moments_simplex(p, fshfs, c_fips, fcips, fwips, fJtips) - - return fcips, fmoments - -end - -function _nfaces_evaluation_points_weights_with_jac(p, fgeomap, fips, wips) - nc = length(fgeomap) - c_fips = fill(fips,nc) - c_wips = fill(wips,nc) - pquad = lazy_map(evaluate,fgeomap,c_fips) - ## Must account for diagonals in simplex discretizations to get the correct - ## scaling - Jt1 = lazy_map(∇,fgeomap) - Jt1_ips = lazy_map(evaluate,Jt1,c_fips) - #det_J = lazy_map(Broadcasting(meas),Jt1_ips) - #c_detwips = collect(lazy_map(Broadcasting(*),c_wips,det_J)) - c_detwips = c_wips - c_fips, pquad, c_detwips, Jt1_ips -end - -function _Nedelec_face_moments_simplex(p, fshfs, c_fips, fcips, fwips, fJtips) - nc = length(c_fips) - cfshfs = fill(fshfs, nc) - cfshfs_fips = lazy_map(evaluate,cfshfs,c_fips) - function weigth(qij,Jti,wi) - Ji = transpose(Jti) - Ji⋅qij*wi - end - cvals = map(Broadcasting(weigth),cfshfs_fips,fJtips,fwips) - return cvals -end - -# It provides for every cell the nodes and the moments arrays -function _Nedelec_cell_values(p,et,order) - - # Compute integration points at interior - degree = 2*(order) - iquad = Quadrature(p,degree) - ccips = get_coordinates(iquad) - cwips = get_weights(iquad) - - # Cell moments, i.e., M(C)_{ab} = q_C^a(xgp_C^b) w_C^b ⋅ () - if is_n_cube(p) - cbasis = QCurlGradMonomialBasis{num_dims(p)}(et,order-1) - else - D = num_dims(p) - cbasis = MonomialBasis{D}(VectorValue{D,et},order-D+1,(e,k)->sum(e)<=k) - end - cmoments = _Nedelec_cell_moments(p, cbasis, ccips, cwips ) - - return [ccips], [cmoments] - -end - -const _Nedelec_cell_moments = _RT_cell_moments - -function _broadcast_cross(::Type{T},n,b) where T - c = Array{T}(undef,size(b)) - for (ii, i) in enumerate(b) - c[ii] = T(cross(get_array(i),get_array(n)))# cross product - end - return c -end - -function _broadcast_extend(::Type{T},Tm,b) where T - c = Array{T}(undef,size(b)) - for (ii,i) in enumerate(b) - c[ii] = T(Tm*[i...]) - end - return c -end - -struct CoVariantPiolaMap <: Map end - -function evaluate!( - cache, - ::Broadcasting{typeof(∇)}, - a::Fields.BroadcastOpFieldArray{CoVariantPiolaMap}) - v, Jt = a.args - # Assuming J comes from an affine map - ∇v = Broadcasting(∇)(v) - k = CoVariantPiolaMap() - Broadcasting(Operation(k))(∇v,Jt) -end - -function lazy_map( - ::Broadcasting{typeof(gradient)}, - a::LazyArray{<:Fill{Broadcasting{Operation{CoVariantPiolaMap}}}}) - v, Jt = a.args - ∇v = lazy_map(Broadcasting(∇),v) - k = CoVariantPiolaMap() - lazy_map(Broadcasting(Operation(k)),∇v,Jt) -end - -function evaluate!(cache,::CoVariantPiolaMap,v::Number,Jt::Number) - v⋅ transpose(pinvJt(Jt))# we multiply by the right side to compute the gradient correctly -end - -function evaluate!(cache,k::CoVariantPiolaMap,v::AbstractVector{<:Field},phi::Field) - Jt = ∇(phi) - Broadcasting(Operation(k))(v,Jt) -end - -function lazy_map( - k::CoVariantPiolaMap, - cell_ref_shapefuns::AbstractArray{<:AbstractArray{<:Field}}, - cell_map::AbstractArray{<:Field}) - - cell_Jt = lazy_map(∇,cell_map) - lazy_map(Broadcasting(Operation(k)),cell_ref_shapefuns,cell_Jt) -end diff --git a/src/ReferenceFEs/PDiscRefFEs.jl b/src/ReferenceFEs/PDiscRefFEs.jl index 6fa399fc7..352e0748c 100644 --- a/src/ReferenceFEs/PDiscRefFEs.jl +++ b/src/ReferenceFEs/PDiscRefFEs.jl @@ -1,18 +1,21 @@ -function _PDiscRefFE(::Type{T},p::Polytope,orders) where T +function _PDiscRefFE(::Type{V},p::Polytope,orders, poly_type) where V order = first(orders) - @notimplementedif any( orders .!= order ) "Anisotropic serentopity FEs not allowed" - _PDiscRefFE(T,p,order) + @notimplementedif any( orders .!= order ) "Anisotropic serendipity FEs not allowed" + _PDiscRefFE(V,p,order, poly_type) end -function _PDiscRefFE(::Type{T},p::Polytope,order::Integer) where T +function _PDiscRefFE(::Type{V},p::Polytope,order::Integer, poly_type) where V + D = num_cell_dims(p) extrusion = tfill(TET_AXIS,Val{D}()) simplex = ExtrusionPolytope(extrusion) - reffe = LagrangianRefFE(T,simplex,order) + reffe = LagrangianRefFE(V,simplex,order) metadata = nothing face_nodes = [Int[] for face in 1:num_faces(p)] face_nodes[end] = collect(1:num_nodes(reffe)) + # basis for ( SᵣΛᴰ(□ᴰ) )ⁿ, n=num_indep_components(V) + basis = FEEC_poly_basis(Val(D),V,order,D,:S,poly_type; cart_prod=(V <: MultiValue)) dofs = get_dof_basis(reffe) face_dofs = _generate_face_own_dofs(face_nodes,dofs.node_and_comp_to_dof) @@ -21,8 +24,8 @@ function _PDiscRefFE(::Type{T},p::Polytope,order::Integer) where T reffe = GenericRefFE{typeof(conf)}( num_dofs(reffe), p, - get_prebasis(reffe), - get_dof_basis(reffe), + basis, # pre-basis + dofs, conf, metadata, face_dofs) diff --git a/src/ReferenceFEs/Polytopes.jl b/src/ReferenceFEs/Polytopes.jl index fb9b0525a..8d2174c28 100644 --- a/src/ReferenceFEs/Polytopes.jl +++ b/src/ReferenceFEs/Polytopes.jl @@ -15,11 +15,16 @@ use *face*. In addition, we say - edge: for 1-faces - facet: for (`D-1`)-faces +The following constants represent the main reference polytopes: +[`VERTEX`](@ref), [`SEGMENT`](@ref) (edge), [`TRI`](@ref) (triangle), +[`QUAD`](@ref) (quadrilateral), [`TET`](@ref) (tetrahedron), [`HEX`](@ref) +(hexahedron), [`WEDGE`](@ref) (triangular prism) and [`PYRAMID`](@ref). + The `Polytope` interface is defined by overloading the following functions - [`get_faces(p::Polytope)`](@ref) - [`get_dimranges(p::Polytope)`](@ref) -- [`Polytope{N}(p::Polytope,faceid::Integer) where N`](@ref) +- [`Polytope{D}(p::Polytope,faceid::Integer) where D`](@ref) - [`get_vertex_coordinates(p::Polytope)`](@ref) - [`(==)(a::Polytope{D},b::Polytope{D}) where D`](@ref) @@ -37,6 +42,8 @@ The interface can be tested with the function - [`test_polytope`](@ref) +There are two implementations, [`ExtrusionPolytope`](@ref) for usual reference +polytopes ⊂ [0,1]ᴰ, and [`GeneralPolytope`](@ref). """ abstract type Polytope{D} <: GridapType end @@ -108,22 +115,21 @@ function get_dimranges(p::Polytope) end """ - get_dimrange(p::Polytope,d::Integer) - -Equivalent to + get_dimrange(p::Polytope, d::Integer) - get_dimranges(p)[d+1] +Indices range of the `d`-dimensional faces of `p`. Equivalent to +[`get_dimranges(p)[d+1]`](@ref get_dimrange). """ function get_dimrange(p::Polytope,d::Integer) get_dimranges(p)[d+1] end """ - Polytope{N}(p::Polytope,faceid::Integer) where N + Polytope{D}(p::Polytope, faceid::Integer) -Returns a `Polytope{N}` object representing the "reference" polytope of the `N`-face with id `faceid`. -The value `faceid` refers to the numeration restricted to the dimension `N` -(it starts with 1 for the first `N`-face). +Returns a `Polytope{D}` object representing the "reference" polytope of the +`D`-face of `p` with id `faceid`. The value `faceid` refers to the numeration +restricted to the dimension `D` (it starts with 1 for the first `D`-face). """ function Polytope{D}(p::Polytope,Dfaceid::Integer) where D @abstractmethod @@ -132,8 +138,7 @@ end """ get_vertex_coordinates(p::Polytope) -> Vector{Point{D,Float64}} -Given a polytope `p` return a vector of points -representing containing the coordinates of the vertices. +Return a vector of points containing the coordinates of the vertices of `p`. """ function get_vertex_coordinates(p::Polytope) @abstractmethod @@ -162,17 +167,18 @@ end get_edge_tangent(p::Polytope) -> Vector{VectorValue{D,Float64}} Given a polytope `p`, returns a vector of `VectorValue` objects -representing the unit tangent vectors to the polytope edges. +representing the unit tangent vectors to the polytope edges, in the order of +[`get_dimrange(p, 1)`](@ref get_dimrange). """ function get_edge_tangent(p::Polytope) @abstractmethod end """ - get_facet_normal(p::Polytope) -> Vector{VectorValue{D,Float64}} + get_facet_normal(p::Polytope{D}) -> Vector{VectorValue{D,Float64}} -Given a polytope `p`, returns a vector of `VectorValue` objects -representing the unit outward normal vectors to the polytope facets. +Return a vector of `VectorValue`s representing the unit *outward* normal vectors +to the facets of `p`. """ function get_facet_normal(p::Polytope) @abstractmethod @@ -181,10 +187,11 @@ end """ get_facet_orientations(p::Polytope) -> Vector{Int} -Given a polytope `p` returns a vector of integers of length `num_facets(p)`. -Facets, whose vertices are ordered consistently with the -outwards normal vector, receive value `1` in this vector. Otherwise, facets -receive value `-1`. +Return a vector of integers of length `num_facets(p)`. +The facets whose vertices are ordered consistently with the outwards normal +vector (w.r.t. right-hand rule convention) have value `1`, and the others `-1`. + +See also [`get_facet_normal`](@ref) and [`get_faces`](@ref). """ function get_facet_orientations(p::Polytope) @abstractmethod @@ -193,10 +200,17 @@ end """ get_vertex_permutations(p::Polytope) -> Vector{Vector{Int}} -Given a polytope `p`, returns a vector of vectors containing all admissible permutations -of the polytope vertices. An admissible permutation is one such that, if the vertices of the polytope -are re-labeled according to this permutation, the resulting polytope preserves the shape of the -original one. +Returns a vector of vectors containing all admissible permutations of the +vertices of `p`. An admissible permutation is one such that, if the vertices of +the polytope are re-labeled according to this permutation, the resulting +polytope preserves the shape of the original one. + +These permutation are used to iddentify every possile way a geometrical map may +permute the vertices of the boundary faces of a reference polytope (of dimension +≤ 3) into the physical one. Only the iddentity permutation `[1, 2, …, N]` is +returned for 3D polytopes with `N` vertices. Indeed, a (3≥D)-dimensional +physical polytope `𝓟` is only mapped by one physical map, but its faces are +mapped by the physical map of all adjascent elements. # Examples @@ -209,6 +223,11 @@ println(perms) # output Array{Int,1}[[1, 2], [2, 1]] +perms = get_vertex_permutations(TET) +println(perms) + +# output +Array{Int,1}[[1, 2], [2, 1]] ``` The first admissible permutation for a segment is `[1,2]`,i.e., the identity. The second one is `[2,1]`, i.e., the first vertex is relabeled as `2` and the @@ -234,7 +253,14 @@ function is_n_cube(p::Polytope) end """ - simplexify(p::Polytope) -> Tuple{Vector{Vector{Int}},Polytope} + simplexify(p::Polytope; kwargs...) -> ( Vector{Vector{Int}}, ref_simplex ) + +Returns a partition of `p` into simplices. The returned couple contains the +vector of each simplex connectivity array, and the reference simplex of the +partition simplices. The node coordinates are that of `p`. + +`ExtrusionPolytope`s of dimension ≤3 support the `positive=true` kwarg. +If set to true, all resulting simplices will keep the orientation of the original polytope. """ function simplexify(p::Polytope;kwargs...) @abstractmethod @@ -242,50 +268,69 @@ end # Some generic API +""" + num_dims(::Type{<:Polytope{D}}) + num_dims(p::Polytope{D}) + +Returns `D`. +""" num_dims(::Type{<:Polytope{D}}) where D = D +num_dims(p::Polytope) = num_dims(typeof(p)) -num_cell_dims(::Type{<:Polytope{D}}) where D = D +""" + num_point_dims(::Type{<:Polytope{D}}) + num_point_dims(p::Polytope{D}) +Returns `D`. +""" num_point_dims(::Type{<:Polytope{D}}) where D = D +num_point_dims(p::Polytope) = num_point_dims(typeof(p)) + """ - num_dims(::Type{<:Polytope{D}}) where D - num_dims(p::Polytope{D}) where D + num_cell_dims(::Type{<:Polytope{D}}) + num_cell_dims(p::Polytope{D}) Returns `D`. """ -num_dims(p::Polytope) = num_dims(typeof(p)) - +num_cell_dims(::Type{<:Polytope{D}}) where D = D num_cell_dims(p::Polytope) = num_cell_dims(typeof(p)) -num_point_dims(p::Polytope) = num_point_dims(typeof(p)) - """ num_faces(p::Polytope) -Returns the total number of faces in polytope `p` (from vertices to the polytope itself). +Returns the total number of faces of `p` (counting `p` itself). + +# Examples + +```jldoctest +num_faces(SEGMENT) + +#output +3 # 2 vertices + 1 edge. +``` """ function num_faces(p::Polytope) length(get_faces(p)) end """ - num_faces(p::Polytope,dim::Integer) + num_faces(p::Polytope, d::Integer) -Returns the number of faces of dimension `dim` in polytope `p`. +Returns the number of faces of dimension `d` of `p`. """ -function num_faces(p::Polytope,dim::Integer) - _num_faces(p,dim) +function num_faces(p::Polytope,d::Integer) + _num_faces(p,d) end -function _num_faces(p,dim) - length(get_dimranges(p)[dim+1]) +function _num_faces(p,d) + length(get_dimranges(p)[d+1]) end """ num_facets(p::Polytope) -Returns the number of facets in the polytope `p`. +Returns the number of facets of `p`. """ function num_facets(p::Polytope) _num_facets(p) @@ -303,7 +348,7 @@ end """ num_edges(p::Polytope) -Returns the number of edges in the polytope `p`. +Returns the number of edges of `p`. """ function num_edges(p::Polytope) _num_edges(p) @@ -322,7 +367,7 @@ end """ num_vertices(p::Polytope) -Returns the number of vertices in the polytope `p`. +Returns the number of vertices of `p`. """ function num_vertices(p::Polytope) _num_vertices(p) @@ -335,8 +380,7 @@ end """ get_facedims(p::Polytope) -> Vector{Int} -Given a polytope `p`, returns a vector indicating -the dimension of each face in the polytope +Return a vector indicating the dimension of each face of `p`. # Examples @@ -371,12 +415,11 @@ function _get_facedims(::Type{T},p) where T end """ - get_offsets(p::Polytope) -> Vector{Int} + get_offsets(p::Polytope{D}) -> Vector{Int} -Given a polytope `p`, it returns a vector of integers. The position in -the `d+1` entry in this vector is the offset that transforms a face id in -the global numeration in the polytope to the numeration restricted to faces -to dimension `d`. +Return a vector whose d+1 entry is the integer offset between a face id in the +global numeration of `p`'s faces to the numeration of `p`'s faces of +dimension d, for d ∈ {0:`D`}. # Examples @@ -387,8 +430,7 @@ offsets = get_offsets(SEGMENT) println(offsets) # output -[0, 2] - +[0, 2] # The first vertex is at 1+0, the first edge at 1+2, in get_faces(SEGMENT) ``` """ function get_offsets(p::Polytope) @@ -410,9 +452,12 @@ function _get_offsets(p) end """ - get_offset(p::Polytope,d::Integer) + get_offset(p::Polytope, d::Integer) -Equivalent to `get_offsets(p)[d+1]`. +Return the integer offset between a face id in the global numeration of `p`'s +faces to the numeration of `p`'s faces of dimension `d`. + +Equivalent to [`get_offsets(p)[d+1]`](@ref get_offsets). """ function get_offset(p::Polytope,d::Integer) _get_offset(p,d) @@ -423,17 +468,19 @@ function _get_offset(p,d) end """ - get_faces(p::Polytope,dimfrom::Integer,dimto::Integer) -> Vector{Vector{Int}} + get_faces(p::Polytope, dimfrom::Integer, dimto::Integer) -> Vector{Vector{Int}} -For `dimfrom >= dimto` returns a vector that for each face of -dimension `dimfrom` stores a vector of the ids of faces of -dimension `dimto` on its boundary. +For `dimfrom >= dimto`, returns a vector that, for each face of `p` of dimension +`dimfrom`, stores a vector of the ids of faces of dimension `dimto` on its boundary. -For `dimfrom < dimto` returns a vector that for each face of `dimfrom` -stores a vector of the face ids of faces of dimension `dimto` that touch it. +For `dimfrom < dimto`, returns a vector that, for each face of `p` of dimension +`dimfrom`, stores a face ids vector containing the ids of the `dimto`-dimensional +faces adjascent to (touching) it. The numerations used in this function are the ones restricted to each dimension. +# Examples + ```jldoctest using Gridap.ReferenceFEs @@ -485,7 +532,30 @@ function _get_faces_dual(p,dimfrom,dimto) end """ - get_face_dimranges(p::Polytope,d::Integer) + get_face_dimranges(p::Polytope) + get_face_dimranges(p::Polytope, d::Integer) + +Return a vector containing, for each face of `p`, the result of +[`get_dimranges`](@ref) on the reference polytope of the face. + +If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `p`. + +# Examples + +2-faces of a pyramid + +```jldoctest +get_face_dimranges(PYRAMID,2) + +# output +5-element Vector{Vector{UnitRange{Int64}}}: + [1:4, 5:8, 9:9] # one QUAD 2-face + [1:3, 4:6, 7:7] # and four TRI 2-faces + [1:3, 4:6, 7:7] + [1:3, 4:6, 7:7] + [1:3, 4:6, 7:7] +``` """ function get_face_dimranges(p::Polytope,d::Integer) n = num_faces(p,d) @@ -509,10 +579,16 @@ end """ get_face_vertices(p::Polytope) -> Vector{Vector{Int}} - get_face_vertices(p::Polytope,dim::Integer) -> Vector{Vector{Int}} + get_face_vertices(p::Polytope, d::Integer) -> Vector{Vector{Int}} + +Return a vector containing, for each face of `p`, the vector of indices of the +vertices of that face. + +If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `p`. """ -function get_face_vertices(p::Polytope,dim::Integer) - get_faces(p,dim,0) +function get_face_vertices(p::Polytope, d::Integer) + get_faces(p,d,0) end function get_face_vertices(p::Polytope) @@ -527,12 +603,30 @@ function get_face_vertices(p::Polytope) end """ - get_reffaces(::Type{Polytope{d}},p::Polytope) where d -> Vector{Polytope{d}} + get_reffaces(p::Polytope) -> Vector{<:Polytope} + get_reffaces(::Type{Polytope{d}}, p::Polytope) -> Vector{<:Polytope{d}} + +Get a vector of the unique reference polytopes for the faces of `p`. -Get a vector of the unique polytopes for the faces of dimension `d`. +If `d` is given, the returned vector contains the polytopes for the +`d`-dimensional faces of `p` only. # Examples +Get the unique polytopes constituting a triangle + +```jldoctest +using Gridap.ReferenceFEs + +reffaces = get_reffaces(TRI) + +println(reffaces) + +# output +ExtrusionPolytope{2}[VERTEX, SEGMENT, TRI] +``` + + Get the unique polytopes for the facets of a wedge. ```jldoctest @@ -543,10 +637,8 @@ reffaces = get_reffaces(Polytope{2},WEDGE) println(reffaces) # output -Gridap.ReferenceFEs.ExtrusionPolytope{2}[TRI, QUAD] - +ExtrusionPolytope{2}[TRI, QUAD] ``` - """ function get_reffaces(::Type{Polytope{d}},p::Polytope) where d ftype_to_refface, = _compute_reffaces_and_face_types(p,Val{d}()) @@ -559,10 +651,14 @@ function get_reffaces(p::Polytope) end """ - get_face_type(p::Polytope,d::Integer) -> Vector{Int} + get_face_type(p::Polytope) -> Vector{Int} + get_face_type(p::Polytope, d::Integer) -> Vector{Int} -Return a vector of integers denoting, for each face of dimension `d`, an index to the -vector `get_reffaces(Polytope{d},p)` +Return a vector containing, for each face of `p`, the index in +`get_reffaces(p)` of the reference polytope of that face. + +If `d` is given, the returned vector contains the indices for the +`d`-dimensional faces of `p` into `get_reffaces(Polytope{d},p)`. # Examples @@ -586,7 +682,6 @@ Gridap.ReferenceFEs.ExtrusionPolytope{2}[TRI, QUAD] ``` The three first facets are of type `1`, i.e, `QUAD`, and the last ones of type `2`, i.e., `TRI`. - """ function get_face_type(p::Polytope,d::Integer) _, iface_to_ftype = _compute_reffaces_and_face_types(p,Val{d}()) @@ -657,7 +752,11 @@ function _find_indexin!(a_to_index, a_to_b, index_to_b,pred::Function=(==)) end """ - get_bounding_box(p::Polytope) + get_bounding_box(p::Polytope{D}) + +Return a couple of `Point{D}`s defining a bounding box containing `p`. The box +is the `D`-cuboid with all edges parallel to the Cartesian axes, and whose +diametraly opposed vertices are the two returned vertices. """ function get_bounding_box(p::Polytope) vertex_to_coords = get_vertex_coordinates(p) @@ -682,7 +781,13 @@ end """ get_face_vertex_permutations(p::Polytope) - get_face_vertex_permutations(p::Polytope,d::Integer) + get_face_vertex_permutations(p::Polytope, d::Integer) + +Return a vector containing, for each face of `p`, the result of +[`get_vertex_permutations`](@ref) on the reference polytope of the face. + +If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `p`. """ function get_face_vertex_permutations(p::Polytope,d::Integer) reffaces = [ Polytope{d}(p, iface) for iface in 1:num_faces(p,d)] @@ -697,7 +802,14 @@ end """ get_face_coordinates(p::Polytope) - get_face_coordinates(p::Polytope,d::Integer) + get_face_coordinates(p::Polytope, d::Integer) + + +Return a vector containing, for each face of `p`, the coordinates of the +vertices of that face. + +If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `p`. """ function get_face_coordinates(p::Polytope,d::Integer) vert_to_coord = get_vertex_coordinates(p) @@ -707,8 +819,11 @@ end function get_face_coordinates(p::Polytope) D = num_cell_dims(p) - p = [ get_face_coordinates(p,d) for d in 0:D ] - vcat(p...) + v = Vector{Point{D,Float64}}[] + for d in 0:D + append!(v, get_face_coordinates(p,d)) + end + v end # Aggregate own data into faces diff --git a/src/ReferenceFEs/Pullbacks.jl b/src/ReferenceFEs/Pullbacks.jl new file mode 100644 index 000000000..5bb2ae0eb --- /dev/null +++ b/src/ReferenceFEs/Pullbacks.jl @@ -0,0 +1,273 @@ + +""" + abstract type Pushforward <: Map end + +Represents a pushforward map ``F_*``, defined as ``F_*`` : V̂ -> V where + - V̂ is a function space on the reference cell K̂ and + - V is a function space on the physical cell K. +""" +abstract type Pushforward <: Map end + +""" + Pushforward(::ReferenceFEName, conf::Conformity) + Pushforward(::Type{<:ReferenceFEName}, conf::Conformity) + +Return the pushforward to use to map the shape functions of the given +element with conformity `conf`. For L2 conformity, the trivial +[`IdentityPiolaMap`](@ref) is always returned. + +For new `ReferenceFEName`, the default pushforward is `IdentityPiolaMap`. Types +that want to change the default may only overload +[`Pushforward(::Type{<:ReferenceFEName})`](@ref). +""" +Pushforward(name::ReferenceFEName, conf::Conformity) = Pushforward(typeof(name),conf) + +Pushforward(::Type{<:ReferenceFEName}, ::L2Conformity) = IdentityPiolaMap() +Pushforward(T::Type{<:ReferenceFEName}, ::Conformity) = Pushforward(T) +Pushforward(::Type{<:ReferenceFEName}) = IdentityPiolaMap() + +function Arrays.lazy_map( + k::Pushforward, ref_cell_fields::AbstractArray, pf_args::AbstractArray... +) + lazy_map(Broadcasting(Operation(k)), ref_cell_fields, pf_args...) +end + +function Arrays.evaluate!( + cache, k::Pushforward, v_ref::Number, args... +) + @abstractmethod +end + +function evaluate!( + cache, k::Pushforward, f_ref::AbstractVector{<:Field}, args... +) + Broadcasting(Operation(k))(f_ref,args...) +end + +function evaluate!( + cache, k::Pushforward, f_ref::Field, args... +) + Operation(k)(f_ref,args...) +end + +function Arrays.lazy_map( + ::Broadcasting{typeof(gradient)}, a::LazyArray{<:Fill{Broadcasting{Operation{<:Pushforward}}}} +) + cell_ref_fields, args... = a.args + cell_ref_gradient = lazy_map(Broadcasting(∇),cell_ref_fields) + return lazy_map(a.maps.value,cell_ref_gradient,args...) +end + +function Arrays.evaluate!( + cache, + ::Broadcasting{typeof(gradient)}, + a::Fields.BroadcastOpFieldArray{<:Pushforward} +) + v, pf_args... = a.args + grad_v = Broadcasting(∇)(v) + Broadcasting(Operation(a.op))(grad_v,pf_args...) +end + +# InversePushforward + +""" + const InversePushforward{PF} = InverseMap{PF} where PF <: Pushforward + +Represents the inverse of a pushforward map ``F_*``, defined as + (``F_*``)⁻¹ : V -> V̂ where + - V̂ is a function space on the reference cell K̂ and + - V is a function space on the physical cell K. +""" +const InversePushforward{PF} = InverseMap{PF} where PF <: Pushforward + +function Arrays.lazy_map( + k::InversePushforward, phys_cell_fields::AbstractArray, pf_args::AbstractArray... +) + lazy_map(Broadcasting(Operation(k)), phys_cell_fields, pf_args...) +end + +function evaluate!( + cache, k::InversePushforward, f_phys::AbstractVector{<:Field}, args... +) + Broadcasting(Operation(k))(f_phys,args...) +end + +function evaluate!( + cache, k::InversePushforward, f_phys::Field, args... +) + Operation(k)(f_phys,args...) +end + +# Pullback + +""" + struct Pullback{PF <: Pushforward} <: Map end + +Represents a pullback map ``F^*``, defined as + ``F^*`` : V* -> V̂* where + - V̂* is a dof space on the reference cell K̂ and + - V* is a dof space on the physical cell K. +Its action on physical dofs σ : V -> R is defined in terms of the pushforward map ``F_*`` as:\\ +σ̂ = ``F^*``(σ) := σ∘``F_*`` : V̂ -> R +""" +struct Pullback{PF <: Pushforward} <: Map + pushforward::PF +end + +function Arrays.lazy_map( + ::typeof(evaluate),k::LazyArray{<:Fill{<:Pullback}},ref_cell_fields::AbstractArray +) + pb = k.maps.value + phys_cell_dofs, pf_args... = k.args + phys_cell_fields = lazy_map(pb.pushforward,ref_cell_fields,pf_args...) + return lazy_map(evaluate,phys_cell_dofs,phys_cell_fields) +end + +function evaluate!( + cache, k::Pullback, σ_phys::AbstractVector{<:Dof}, args... +) + return MappedDofBasis(k.pushforward,σ_phys,args...) +end + +# InversePullback + +""" + struct InversePullback{PF <: Pushforward} <: Map end + +Represents the inverse of the pullback map ``(F^*)``⁻¹, defined as + ``(F^*)``⁻¹ : V̂* -> V* +where + - V̂* is a dof space on the reference cell K̂ and + - V* is a dof space on the physical cell K. +Its action on reference dofs σ̂ : V̂ -> R is defined in terms of the pushforward map ``F_*`` as:\\ +σ = ``(F^*)``⁻¹(σ̂) := σ̂∘``(F_*)``⁻¹ : V -> R +""" +const InversePullback{PB} = InverseMap{PB} where PB <: Pullback + +function Arrays.lazy_map( + ::typeof(evaluate), k::LazyArray{<:Fill{<:InversePullback}}, phys_cell_fields::AbstractArray +) + pb = inverse_map(k.maps.value) + ref_cell_dofs, pf_args... = k.args + ref_cell_fields = lazy_map(inverse_map(pb.pushforward), phys_cell_fields, pf_args...) + return lazy_map(evaluate,ref_cell_dofs,ref_cell_fields) +end + +function evaluate!( + cache, k::InversePullback, σ_ref::AbstractVector{<:Dof}, args... +) + pb = inverse_map(k) + return MappedDofBasis(inverse_map(pb.pushforward),σ_ref,args...) +end + +############## +# Piola maps # +############## + +# In what follows, +# - F is the geometrical map F:K̂->K +# - Jt = ∇F = (Jac(F))ᵀ + +""" + struct IdentityPiolaMap <: Pushforward +""" +struct IdentityPiolaMap <: Pushforward end # φ̂ -> φ = φ̂∘F⁻¹ + +# ContraVariantPiolaMap + +""" + struct ContraVariantPiolaMap <: Pushforward +""" +struct ContraVariantPiolaMap <: Pushforward end + +function evaluate!( # φ̂ -> φ = (|det(J)|⁻¹J φ̂)∘F⁻¹ + cache, ::ContraVariantPiolaMap, v_ref::Number, Jt::Number +) + idetJ = 1. / meas(Jt) + return v_ref ⋅ (idetJ * Jt) +end + +function evaluate!( # φ -> φ̂ = |det(J)| J⁻¹ φ∘F + cache, ::InversePushforward{ContraVariantPiolaMap}, v_phys::Number, Jt::Number +) + detJ = meas(Jt) + return v_phys ⋅ (detJ * pinvJt(Jt)) +end + +# TODO: Should this be here? Probably not... + +function Fields.DIV(f::LazyArray{<:Fill}) + df = Fields.DIV(f.args[1]) + k = f.maps.value + lazy_map(k,df) +end + +function Fields.DIV(a::LazyArray{<:Fill{typeof(linear_combination)}}) + i_to_basis = Fields.DIV(a.args[2]) + i_to_values = a.args[1] + lazy_map(linear_combination,i_to_values,i_to_basis) +end + +function Fields.DIV(f::LazyArray{<:Fill{Broadcasting{Operation{ContraVariantPiolaMap}}}}) + ϕrgₖ = f.args[1] + return lazy_map(Broadcasting(divergence),ϕrgₖ) +end + +function Fields.DIV(f::Fill{<:Fields.BroadcastOpFieldArray{ContraVariantPiolaMap}}) + ϕrgₖ = f.value.args[1] + return Fill(Broadcasting(divergence)(ϕrgₖ),length(f)) +end + +# CoVariantPiolaMap + +""" + struct CoVariantPiolaMap <: Pushforward +""" +struct CoVariantPiolaMap <: Pushforward end + +function evaluate!( # φ̂ -> φ = (J⁻ᵀ φ̂)∘F⁻¹ + cache, ::CoVariantPiolaMap, v_ref::Number, Jt::Number +) + return v_ref ⋅ transpose(pinvJt(Jt)) +end + +function evaluate!( # φ -> φ̂ = Jᵀ φ∘F + cache, ::InversePushforward{CoVariantPiolaMap}, v_phys::Number, Jt::Number +) + return v_phys ⋅ transpose(Jt) +end + +# DoubleContraVariantPiolaMap + +struct DoubleContraVariantPiolaMap <: Pushforward end + +function evaluate!( # φ̂ -> φ = (det(J)⁻² J φ̂ Jᵀ)∘F⁻¹ + cache, ::DoubleContraVariantPiolaMap, v_ref::Number, Jt::Number +) + _Jt = (1. / det(Jt)) * Jt + return congruent_prod(v_ref, _Jt) # symmetry stable _Jtᵀ ⋅ v_ref ⋅ _Jt +end + +function evaluate!( # φ -> φ̂ = det(J)² J⁻¹ φ∘F J⁻ᵀ + cache, ::InversePushforward{DoubleContraVariantPiolaMap}, v_phys::Number, Jt::Number +) + iJt = det(Jt) * pinvJt(Jt) + return congruent_prod(v_phys, iJt) # symmetry stable iJtᵀ ⋅ v_ref ⋅ iJt +end + +# DoubleCoVariantPiolaMap + +struct DoubleCoVariantPiolaMap <: Pushforward end + +function evaluate!( # φ̂ -> φ = (J⁻ᵀ φ̂ J⁻¹)∘F⁻¹ + cache, ::DoubleCoVariantPiolaMap, v_ref::Number, Jt::Number +) + iJt = pinvJt(Jt) + return congruent_prod(v_ref, transpose(iJt)) # symmetry stable iJt ⋅ v_ref ⋅ iJtᵀ +end + +function evaluate!( # φ -> φ̂ = Jᵀ φ∘F J + cache, ::InversePushforward{DoubleCoVariantPiolaMap}, v_phys::Number, Jt::Number +) + return congruent_prod(v_ref, transpose(Jt)) # symmetry stable Jt ⋅ v_phys ⋅ Jtᵀ +end diff --git a/src/ReferenceFEs/Quadratures.jl b/src/ReferenceFEs/Quadratures.jl index 275050166..a026ce30b 100644 --- a/src/ReferenceFEs/Quadratures.jl +++ b/src/ReferenceFEs/Quadratures.jl @@ -1,10 +1,27 @@ """ - abstract type Quadrature{D,T} <: GridapType end + abstract type Quadrature{D,T} <: GridapType + +Abstract type representing a quadrature rule. + +Instances of this type should implement the following API: + + - [`get_coordinates(q::Quadrature)`](@ref) + - [`get_weights(q::Quadrature)`](@ref) + - [`get_name(q::Quadrature)`](@ref) + +The following methods are implemented by default: + + - [`num_points(q::Quadrature)`](@ref) + - [`num_point_dims(q::Quadrature)`](@ref) + - [`num_dims(q::Quadrature)`](@ref) + - [`test_quadrature`](@ref) + +To include a new quadrature in the factory, one should define a new `QuadratureName` subtype +and a new factory method: + + Quadrature(p::Polytope,name::QuadratureName,args...;kwargs...) --[`get_coordinates(q::Quadrature)`](@ref) --[`get_weights(q::Quadrature)`](@ref) --[`test_quadrature`](@ref) """ abstract type Quadrature{D,T} <: GridapType end @@ -12,6 +29,8 @@ abstract type Quadrature{D,T} <: GridapType end """ get_coordinates(q::Quadrature) + +Returns the quadrature points. """ function get_coordinates(q::Quadrature) @abstractmethod @@ -19,6 +38,8 @@ end """ get_weights(q::Quadrature) + +Returns the quadrature weights. """ function get_weights(q::Quadrature) @abstractmethod @@ -26,6 +47,8 @@ end """ get_name(q::Quadrature) + +Returns the name of the quadrature. """ function get_name(q::Quadrature) @abstractmethod @@ -39,16 +62,16 @@ end num_points(q::Quadrature) = length(get_weights(q)) """ - num_point_dims(::Quadrature{D}) where D - num_point_dims(::Type{<:Quadrature{D}}) where D + num_point_dims(::Quadrature{D}) + num_point_dims(::Type{<:Quadrature{D}}) """ num_point_dims(::Quadrature{D}) where D = D num_point_dims(::Type{<:Quadrature{D}}) where D = D """ - num_dims(::Quadrature{D}) where D where D - num_dims(::Type{<:Quadrature{D}}) where D + num_dims(::Quadrature{D}) + num_dims(::Type{<:Quadrature{D}}) """ num_dims(::Quadrature{D}) where D = D @@ -57,7 +80,7 @@ num_dims(::Type{<:Quadrature{D}}) where D = D # Tester """ - test_quadrature(q::Quadrature{D,T}) where {D,T} + test_quadrature(q::Quadrature{D,T}) """ function test_quadrature(q::Quadrature{D,T}) where {D,T} x = get_coordinates(q) @@ -70,12 +93,15 @@ function test_quadrature(q::Quadrature{D,T}) where {D,T} @test D == num_point_dims(q) @test D == num_dims(typeof(q)) @test D == num_point_dims(typeof(q)) + @test all(x -> x > 0, w) end # Generic concrete implementation """ - struct GenericQuadrature{D,T,C <: AbstractVector{Point{D,T}},W <: AbstractVector{T}} <: Quadrature{D,T} + struct GenericQuadrature{D,T, + C <: AbstractVector{Point{D,T}}, W <: AbstractVector{T}} <: Quadrature{D,T} + coordinates::Vector{Point{D,T}} weights::Vector{T} name::String @@ -109,6 +135,12 @@ end # Quadrature factory +""" + abstract type QuadratureName + +Supertype of all singleton types representing a quadrature name, e.g. +[`tensor_product`](@ref). +""" abstract type QuadratureName end @noinline function Quadrature(p::Polytope,name::QuadratureName,args...;kwargs...) @@ -120,7 +152,7 @@ end Quadrature(name::QuadratureName,args...;kwargs...) = (name, args, kwargs) """ - Quadrature(polytope::Polytope{D},degree) where D + Quadrature(polytope::Polytope{D}, degree) """ function Quadrature(p::Polytope,degree;T::Type{<:AbstractFloat}=Float64) if is_n_cube(p) diff --git a/src/ReferenceFEs/RaviartThomasRefFEs.jl b/src/ReferenceFEs/RaviartThomasRefFEs.jl index d4388ea94..a012ca7a7 100644 --- a/src/ReferenceFEs/RaviartThomasRefFEs.jl +++ b/src/ReferenceFEs/RaviartThomasRefFEs.jl @@ -1,60 +1,73 @@ -struct DivConformity <: Conformity end -abstract type DivConforming <: ReferenceFEName end +# RaviartThomas -struct RaviartThomas <: DivConforming end +""" + struct RaviartThomas <: ReferenceFEName +""" +struct RaviartThomas <: ReferenceFEName end + +""" + const raviart_thomas = RaviartThomas() +Singleton of the [`RaviartThomas`](@ref) reference FE name. +""" const raviart_thomas = RaviartThomas() +Pushforward(::Type{RaviartThomas}) = ContraVariantPiolaMap() + """ - RaviartThomasRefFE(::Type{et},p::Polytope,order::Integer) where et + RaviartThomasRefFE(::Type{T}, p::Polytope, order::Integer; kwargs...) -The `order` argument has the following meaning: the divergence of the functions in this basis -is in the Q space of degree `order`. +The `order` argument has the following meaning: the divergence of the functions +in this basis is in the Q space of degree `order`. `T` is the type of scalar components. +The `kwargs` are [`change_dof`](@ref "`change_dof` keyword argument"), +[`poly_type`](@ref "`poly_type` keyword argument") and +[`mom_poly_type`](@ref "`mom_poly_type` keyword argument"). """ -function RaviartThomasRefFE(::Type{et},p::Polytope,order::Integer) where et +function RaviartThomasRefFE( + ::Type{T},p::Polytope{D},order::Integer; + change_dof=true, poly_type=_mom_reffe_default_PT(p), mom_poly_type=poly_type) where {T,D} - D = num_dims(p) + PT, MPT = poly_type, mom_poly_type + rotate_90 = D==2 + k = D-1 if is_n_cube(p) - prebasis = QCurlGradMonomialBasis{D}(et,order) + prebasis = FEEC_poly_basis(Val(D), T,order+1,k,:Q⁻,PT; rotate_90) # Q⁻ᵣΛᵏ(□ᴰ), r = order+1 + fb = FEEC_poly_basis(Val(D-1),T,order ,0,:Q⁻,MPT) # Facet basis Q⁻ᵨΛ⁰(□ᴰ⁻¹), ρ = r-1 + cb = order>0 ? FEEC_poly_basis(Val(D), T,order ,1,:Q⁻,MPT) : nothing # Cell basis Q⁻ᵨΛ¹(□ᴰ), ρ = r-1 elseif is_simplex(p) - prebasis = PCurlGradMonomialBasis{D}(et,order) + prebasis = FEEC_poly_basis(Val(D), T,order+1,k,:P⁻,PT; rotate_90) # P⁻ᵣΛᵏ(△ᴰ), r = order+1 + fb = FEEC_poly_basis(Val(D-1),T,order ,0,:P ,MPT) # Facet basis PᵨΛ⁰(△ᴰ⁻¹), ρ = r-1 + cb = order>0 ? FEEC_poly_basis(Val(D), T,order-1,1,:P ,MPT) : nothing # Cell basis PᵨΛ¹(△ᴰ), ρ = r-2 else - @notimplemented "H(div) Reference FE only available for cubes and simplices" + @notimplemented "Raviart-Thomas Reference FE only available for n-cubes and simplices" end - nf_nodes, nf_moments = _RT_nodes_and_moments(et,p,order,GenericField(identity)) - - face_own_dofs = _face_own_dofs_from_moments(nf_moments) - - face_dofs = face_own_dofs - - dof_basis = MomentBasedDofBasis(nf_nodes, nf_moments) - - ndofs = num_dofs(dof_basis) - - metadata = nothing - - reffe = GenericRefFE{RaviartThomas}( - ndofs, - p, - prebasis, - dof_basis, - DivConformity(), - metadata, - face_dofs) + function cmom(φ,μ,ds) # Cell moment function: σ_K(φ,μ) = ∫(φ·μ)dK + Broadcasting(Operation(⋅))(φ,μ) + end + function fmom(φ,μ,ds) # Face moment function : σ_F(φ,μ) = ∫((φ·n)*μ)dF + n = get_facet_normal(ds) + φn = Broadcasting(Operation(⋅))(φ,n) + Broadcasting(Operation(*))(φn,μ) + end - reffe -end + moments = Tuple[ + (get_dimrange(p,D-1),fmom,fb), # Face moments + ] + if (order > 0) + push!(moments,(get_dimrange(p,D),cmom,cb)) # Cell moments + end -function ReferenceFE(p::Polytope,::RaviartThomas, order) - RaviartThomasRefFE(Float64,p,order) + conf = DivConformity() + change_dof = _validate_change_dof(change_dof, prebasis, p, conf) + return MomentBasedReferenceFE(raviart_thomas,p,prebasis,moments,conf; change_dof) end -function ReferenceFE(p::Polytope,::RaviartThomas,::Type{T}, order) where T - RaviartThomasRefFE(T,p,order) +function ReferenceFE(p::Polytope,::RaviartThomas,::Type{T},order; kwargs...) where T + RaviartThomasRefFE(T,p,order; kwargs...) end function Conformity(reffe::GenericRefFE{RaviartThomas},sym::Symbol) @@ -75,353 +88,3 @@ end function get_face_own_dofs(reffe::GenericRefFE{RaviartThomas}, conf::DivConformity) get_face_dofs(reffe) end - -function _RT_nodes_and_moments(::Type{et}, p::Polytope, order::Integer, phi::Field) where et - - D = num_dims(p) - ft = VectorValue{D,et} - pt = Point{D,et} - - nf_nodes = [ zeros(pt,0) for face in 1:num_faces(p)] - nf_moments = [ zeros(ft,0,0) for face in 1:num_faces(p)] - - fcips, fmoments = _RT_face_values(p,et,order,phi) - frange = get_dimrange(p,D-1) - nf_nodes[frange] = fcips - nf_moments[frange] = fmoments - - if (order > 0) - ccips, cmoments = _RT_cell_values(p,et,order,phi) - crange = get_dimrange(p,D) - nf_nodes[crange] = ccips - nf_moments[crange] = cmoments - end - - nf_nodes, nf_moments -end - -# Ref FE to faces geomaps -function _ref_face_to_faces_geomap(p,fp) - cfvs = get_face_coordinates(p,num_dims(fp)) - nc = length(cfvs) - freffe = LagrangianRefFE(Float64,fp,1) - fshfs = get_shapefuns(freffe) - cfshfs = fill(fshfs, nc) - fgeomap = lazy_map(linear_combination,cfvs,cfshfs) -end - -function _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) - nc = length(fgeomap) - c_fips = fill(fips,nc) - c_wips = fill(wips,nc) - pquad = lazy_map(evaluate,fgeomap,c_fips) - - if is_n_cube(p) - c_detwips = c_wips - elseif is_simplex(p) - # Must account for diagonals in simplex discretizations to get the correct - # scaling - Jt1 = lazy_map(∇,fgeomap) - Jt1_ips = lazy_map(evaluate,Jt1,c_fips) - det_J = lazy_map(Broadcasting(meas),Jt1_ips) - - c_detwips = collect(lazy_map(Broadcasting(*),c_wips,det_J)) - end - - c_fips, pquad, c_detwips -end - -function _broadcast(::Type{T},n,b) where T - c = Array{T}(undef,size(b)) - for (ii, i) in enumerate(b) - c[ii] = i⋅n - end - return c -end - -function _RT_face_moments(p, fshfs, c_fips, fcips, fwips,phi) - nc = length(c_fips) - cfshfs = fill(fshfs, nc) - cvals = lazy_map(evaluate,cfshfs,c_fips) - cvals = [fwips[i].*cvals[i] for i in 1:nc] - fns = get_facet_normal(p) - - # Must express the normal in terms of the real/reference system of - # coordinates (depending if phi≡I or phi is a mapping, resp.) - # Hence, J = transpose(grad(phi)) - - Jt = fill(∇(phi),nc) - Jt_inv = lazy_map(Operation(pinvJt),Jt) - det_Jt = lazy_map(Operation(meas),Jt) - change = lazy_map(*,det_Jt,Jt_inv) - change_ips = lazy_map(evaluate,change,fcips) - - cvals = [ _broadcast(typeof(n),n,J.*b) for (n,b,J) in zip(fns,cvals,change_ips)] - - return cvals -end - -# It provides for every face the nodes and the moments arrays -function _RT_face_values(p,et,order,phi) - - # Reference facet - @assert is_simplex(p) || is_n_cube(p) "We are assuming that all n-faces of the same n-dim are the same." - fp = Polytope{num_dims(p)-1}(p,1) - - # geomap from ref face to polytope faces - fgeomap = _ref_face_to_faces_geomap(p,fp) - - # Nodes are integration points (for exact integration) - # Thus, we define the integration points in the reference - # face polytope (fips and wips). Next, we consider the - # n-face-wise arrays of nodes in fp (constant cell array c_fips) - # the one of the points in the polytope after applying the geopmap - # (fcips), and the weights for these nodes (fwips, a constant cell array) - # Nodes (fcips) - degree = (order)*2 - fquad = Quadrature(fp,degree) - fips = get_coordinates(fquad) - wips = get_weights(fquad) - - c_fips, fcips, fwips = _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) - - # Moments (fmoments) - # The RT prebasis is expressed in terms of shape function - fshfs = MonomialBasis(et,fp,order) - - # Face moments, i.e., M(Fi)_{ab} = q_RF^a(xgp_RFi^b) w_Fi^b n_Fi ⋅ () - fmoments = _RT_face_moments(p, fshfs, c_fips, fcips, fwips, phi) - - return fcips, fmoments - -end - -function _RT_cell_moments(p, cbasis, ccips, cwips) - # Interior DOFs-related basis evaluated at interior integration points - ishfs_iips = evaluate(cbasis,ccips) - return cwips.⋅ishfs_iips -end - -_p_filter(e,order) = (sum(e) <= order) - -# It provides for every cell the nodes and the moments arrays -function _RT_cell_values(p,et,order,phi) - # Compute integration points at interior - degree = 2*(order) - iquad = Quadrature(p,degree) - ccips = get_coordinates(iquad) - cwips = get_weights(iquad) - - # Cell moments, i.e., M(C)_{ab} = q_C^a(xgp_C^b) w_C^b ⋅ () - if is_n_cube(p) - cbasis = QGradMonomialBasis{num_dims(p)}(et,order-1) - elseif is_simplex(p) - T = VectorValue{num_dims(p),et} - cbasis = MonomialBasis{num_dims(p)}(T,order-1, _p_filter) - else - @notimplemented - end - cell_moments = _RT_cell_moments(p, cbasis, ccips, cwips ) - - # Must scale weights using phi map to get the correct integrals - # scaling = meas(grad(phi)) - Jt = ∇(phi) - Jt_inv = pinvJt(Jt) - det_Jt = meas(Jt) - change = det_Jt*Jt_inv - change_ips = evaluate(change,ccips) - - cmoments = change_ips.⋅cell_moments - - return [ccips], [cmoments] - -end - -function _face_own_dofs_from_moments(f_moments) - face_dofs = Vector{Int}[] - o = 1 - for moments in f_moments - ndofs = size(moments,2) - dofs = collect(o:(o+ndofs-1)) - push!(face_dofs,dofs) - o += ndofs - end - face_dofs -end - -struct Moment <: Dof end - -struct MomentBasedDofBasis{P,V} <: AbstractVector{Moment} - nodes::Vector{P} - face_moments::Vector{Array{V}} - face_nodes::Vector{UnitRange{Int}} - - function MomentBasedDofBasis(nodes,f_moments,f_nodes) - P = eltype(nodes) - V = eltype(eltype(f_moments)) - new{P,V}(nodes,f_moments,f_nodes) - end - - function MomentBasedDofBasis(f_nodes,f_moments) - P = eltype(eltype(f_nodes)) - V = eltype(eltype(f_moments)) - nodes = P[] - face_nodes = UnitRange{Int}[] - nfaces = length(f_nodes) - n = 1 - for fi in 1:nfaces - nodes_fi = f_nodes[fi] - nini = n - for node_fi in nodes_fi - push!(nodes,node_fi) - n += 1 - end - nend = n-1 - push!(face_nodes,nini:nend) - end - new{P,V}(nodes,f_moments,face_nodes) - end -end - -Base.size(a::MomentBasedDofBasis) = (length(a.nodes),) -Base.axes(a::MomentBasedDofBasis) = (axes(a.nodes,1),) -# @santiagobadia : Not sure we want to create the moment dofs -Base.getindex(a::MomentBasedDofBasis,i::Integer) = Moment() -Base.IndexStyle(::MomentBasedDofBasis) = IndexLinear() - -get_nodes(b::MomentBasedDofBasis) = b.nodes -get_face_moments(b::MomentBasedDofBasis) = b.face_moments -get_face_nodes_dofs(b::MomentBasedDofBasis) = b.face_nodes - -function num_dofs(b::MomentBasedDofBasis) - n = 0 - for m in b.face_moments - n += size(m,2) - end - n -end - -function return_cache(b::MomentBasedDofBasis,field) - cf = return_cache(field,b.nodes) - vals = evaluate!(cf,field,b.nodes) - ndofs = num_dofs(b) - r = _moment_dof_basis_cache(vals,ndofs) - c = CachedArray(r) - (c, cf) -end - -function _moment_dof_basis_cache(vals::AbstractVector,ndofs) - T = eltype(vals) - r = fill(zero(eltype(T))*0.,ndofs) -end - -function _moment_dof_basis_cache(vals::AbstractMatrix,ndofs) - _, npdofs = size(vals) - T = eltype(vals) - r = fill(zero(eltype(T))*0.,ndofs,npdofs) -end - -function evaluate!(cache,b::MomentBasedDofBasis,field) - c, cf = cache - vals = evaluate!(cf,field,b.nodes) - dofs = c.array - _eval_moment_dof_basis!(dofs,vals,b) - dofs -end - -function _eval_moment_dof_basis!(dofs,vals::AbstractVector,b) - o = 1 - z = zero(eltype(dofs)) - face_nodes = b.face_nodes - face_moments = b.face_moments - for face in 1:length(face_moments) - moments = face_moments[face] - if length(moments) != 0 - nodes = face_nodes[face] - ni,nj = size(moments) - for j in 1:nj - dofs[o] = z - for i in 1:ni - dofs[o] += moments[i,j]⋅vals[nodes[i]] - end - o += 1 - end - end - end -end - -function _eval_moment_dof_basis!(dofs,vals::AbstractMatrix,b) - o = 1 - na = size(vals,2) - z = zero(eltype(dofs)) - face_nodes = b.face_nodes - face_moments = b.face_moments - for face in 1:length(face_moments) - moments = face_moments[face] - if length(moments) != 0 - nodes = face_nodes[face] - ni,nj = size(moments) - for j in 1:nj - for a in 1:na - dofs[o,a] = z - for i in 1:ni - dofs[o,a] += moments[i,j]⋅vals[nodes[i],a] - end - end - o += 1 - end - end - end -end - -struct ContraVariantPiolaMap <: Map end - -function evaluate!( - cache, - ::Broadcasting{typeof(∇)}, - a::Fields.BroadcastOpFieldArray{ContraVariantPiolaMap}) - v, Jt, detJ,sign_flip = a.args - # Assuming J comes from an affine map - ∇v = Broadcasting(∇)(v) - k = ContraVariantPiolaMap() - Broadcasting(Operation(k))(∇v,Jt,detJ,sign_flip) -end - -function lazy_map( - ::Broadcasting{typeof(gradient)}, - a::LazyArray{<:Fill{Broadcasting{Operation{ContraVariantPiolaMap}}}}) - v, Jt, detJ,sign_flip = a.args - ∇v = lazy_map(Broadcasting(∇),v) - k = ContraVariantPiolaMap() - lazy_map(Broadcasting(Operation(k)),∇v,Jt,detJ,sign_flip) -end - -function evaluate!(cache,::ContraVariantPiolaMap, - v::Number, - Jt::Number, - detJ::Number, - sign_flip::Bool) - ((-1)^sign_flip*v)⋅((1/detJ)*Jt) -end - -function evaluate!(cache, - k::ContraVariantPiolaMap, - v::AbstractVector{<:Field}, - phi::Field, - sign_flip::AbstractVector{<:Field}) - Jt = ∇(phi) - detJ = Operation(meas)(Jt) - Broadcasting(Operation(k))(v,Jt,detJ,sign_flip) -end - -function lazy_map( - k::ContraVariantPiolaMap, - cell_ref_shapefuns::AbstractArray{<:AbstractArray{<:Field}}, - cell_map::AbstractArray{<:Field}, - sign_flip::AbstractArray{<:AbstractArray{<:Field}}) - - cell_Jt = lazy_map(∇,cell_map) - cell_detJ = lazy_map(Operation(meas),cell_Jt) - - lazy_map(Broadcasting(Operation(k)),cell_ref_shapefuns,cell_Jt,cell_detJ,sign_flip) -end diff --git a/src/ReferenceFEs/ReferenceFEInterfaces.jl b/src/ReferenceFEs/ReferenceFEInterfaces.jl index 11cc6423b..342522394 100644 --- a/src/ReferenceFEs/ReferenceFEInterfaces.jl +++ b/src/ReferenceFEs/ReferenceFEInterfaces.jl @@ -1,20 +1,63 @@ +""" + abstract type Conformity + +`Conformity` subtypes are singletons representing a FE space conformity, that is +a Sobolev space that the global finite element space is a subset of. + +The available conformities, along with the `Symbol`s that some high level +constructors accept, are: +- [`L2Conformity`](@ref); `:L2` +- [`GradConformity`](@ref) (alias `H1Conformity`); `:H1`, `:Hgrad`, `:C0` +- [`CurlConformity`](@ref); `:Hcurl`, `:HCurl` +- [`DivConformity`](@ref); `:Hdiv`, `:HDiv` +- [`CDConformity`](@ref) +""" abstract type Conformity end +""" + struct L2Conformity <: Conformity +""" struct L2Conformity <: Conformity end +""" + struct GradConformity <: Conformity +""" +struct GradConformity <: Conformity end +const H1Conformity = GradConformity + +""" + struct CurlConformity <: Conformity +""" +struct CurlConformity <: Conformity end + +""" + struct DivConformity <: Conformity +""" +struct DivConformity <: Conformity end + + """ abstract type ReferenceFE{D} <: GridapType -Abstract type representing a Reference finite element. `D` is the underlying coordinate space dimension. -We follow the Ciarlet definition. A reference finite element -is defined by a polytope (cell topology), a basis of an interpolation space -of top of this polytope (denoted here as the prebasis), and a basis of the dual of this space -(i.e. the degrees of freedom). From this information one can compute the shape functions -(i.e, the canonical basis of w.r.t. the degrees of freedom) with a simple change of basis. -In addition, we also encode in this type information about how the interpolation space -in a reference finite element is "glued" with neighbors in order to build conforming -cell-wise spaces. +Abstract type representing a Reference finite element. `D` is the underlying +coordinate space dimension. We follow the Ciarlet definition. +A reference finite element is defined by a polytope (cell topology), a basis of +an interpolation space of top of this polytope (denoted here as the prebasis), +and a basis of the dual of this space , i.e. the Degrees of Freedom (DoFs). +From this information one can compute the shape functions, i.e the canonical +basis w.r.t. the DoFs via `compute_shapefuns`. It is also possible to use +polynomial basis as shape functions, and compute the DoFs basis from a +prebasis of DoFs via `compute_dofs`. In the end, any `reffe::ReferenceFE` should +verify that + + evaluate(get_dof_basis(reffe), get_shapefuns(reffe)) + +is approximately the identity matrix. + +In addition, this type encodes information about how the shape functions of the +reference finite element are "glued" with that of neighbors in order to build +globally conforming spaces, c.f. [`get_face_own_dofs`](@ref) and [`get_face_own_dofs_permutations`](@ref). The `ReferenceFE` interface is defined by overloading these methods: @@ -33,21 +76,82 @@ The interface is tested with """ abstract type ReferenceFE{D} <: GridapType end +""" + abstract type ReferenceFEName + +Supertype for the reference finite element name singleton types, e.g. [`lagrangian`](@ref). +Instances are used to select a reference FE in the [`ReferenceFE`](@ref ReferenceFE(n::ReferenceFEName,a...;k...)) constructor. +""" abstract type ReferenceFEName end # Extensible factory function -function ReferenceFE(p::Polytope,basis::ReferenceFEName,args...;kwargs...) - @unreachable """\n - Undefined factory function ReferenceFE for basis $basis and the given arguments. + +""" + ReferenceFE(name::ReferenceFEName[, T::Type], order; kwargs...) + ReferenceFE(name::ReferenceFEName[, T::Type], orders; kwargs...) + ReferenceFE(F::Symbol, r, k, [, T::Type]; kwargs...) + +Signatures defining a reference finite element (but yet unspecified cell polytope) from: +- an element [`name`](@ref ReferenceFEName), value type `T` and `order(s)`, or +- a FEEC family `F ∈ (:P⁻, :P, :Q⁻, :S)`, with polynomial order `r` and form order `k`. + +# Arguments +- `T`: type of scalar components of the shape function values, `Float64` by default. + For elements supporting Cartesian product space of a scalar one (e.g. [`lagrangian`](@ref)), this can be a tensor type like `VectorValue{2,Float64}`. +- `order::Int`: the polynomial order parameter, or +- `orders::NTuple{D,Int}`: a tuple of order per space dimension for anysotropic elements. + +Keyword arguments are element specific, except +- `rotate_90::Bool=false`, set to true for div-conforming FEEC bases in 2D (only if k=1). +- `nodal::Bool=false`, for FEEC constructor, choice between moment DOFs ([`ModalScalar`](@ref ModalScalarRefFE) FEs) or Lagrangian/node-based DOFs ([`lagrangian`](@ref)/[`serendipity`](@ref)). + +!!! warning + This method only returns the tuple of its arguments, the actual Reference + FE(s) is(are) only built once the polytope(s) is(are) known. See the other + `ReferenceFE` methods or the FESpaces constructors. +""" +ReferenceFE(name::ReferenceFEName, args...; kwargs...) = (name, args, kwargs) +ReferenceFE(F::Symbol, args...; kwargs...) = (F, args, kwargs) + +""" + ReferenceFE(p::Polytope, args...; kwargs...) + +Return the specified reference FE implemented on `p`. + +The `args` and `kwargs` are the arguments of +[`ReferenceFE(::ReferenceFEName, ...; ...)`](@ref +ReferenceFE(::ReferenceFEName,a...;k...)) or [`ReferenceFE(F::Symbol, ...; ...)`](@ref +ReferenceFE(::Symbol,a...;k...)), first argument included. +""" +function ReferenceFE(p::Polytope, args...; kwargs...) + @unreachable """ + Undefined factory function ReferenceFE for the given arguments: + - args: $args, + - kwargs: $kwargs. """ end +# Default component/value type +function ReferenceFE(p::Polytope, name::ReferenceFEName, order; kwargs...) + ReferenceFE(p,name,Float64,order; kwargs...) +end +function ReferenceFE(p::Polytope,F::Symbol,r,k; kwargs...) + ReferenceFE(p,F,r,k,Float64; kwargs...) # implemented in ExteriorCalculusRefFEs.jl +end -ReferenceFE(basis::ReferenceFEName,args...;kwargs...) = (basis, args, kwargs) +""" + get_name(::ReferenceFE) + get_name(::Type{ReferenceFE}) + +Returns the [ReferenceFEName](@ref) of the given reference FE. +""" +get_name(::Type{<:ReferenceFE})::ReferenceFEName = @abstractmethod +get_name(reffe::ReferenceFE) = get_name(typeof(reffe)) """ num_dofs(reffe::ReferenceFE) -> Int -Returns the number of DOFs. +Returns the number of DoFs, that is also the number of shape functions and the +dimension of the element's polynomial space. """ function num_dofs(reffe::ReferenceFE) @abstractmethod @@ -56,27 +160,35 @@ end """ get_polytope(reffe::ReferenceFE) -> Polytope -Returns the underlying polytope object. +Returns the underlying [`Polytope`](@ref) object. """ function get_polytope(reffe::ReferenceFE) @abstractmethod end """ - get_prebasis(reffe::ReferenceFE) -> Field + get_prebasis(reffe::ReferenceFE) -Returns the underlying prebasis encoded as a `Field` object. +Returns the polynomial basis used as a pre-basis to compute the shape function, +which is a vector of `Field`s, often a [`PolynomialBasis`](@ref). """ function get_prebasis(reffe::ReferenceFE) @abstractmethod end +""" + get_order(reffe::ReferenceFE) -> Int + +Returns the maximum polynomial order of shape function of `reffe`. +""" get_order(reffe::ReferenceFE) = get_order(get_prebasis(reffe)) """ - get_dof_basis(reffe::ReferenceFE) -> Dof + get_dof_basis(reffe::ReferenceFE) -Returns the underlying dof basis encoded in a `Dof` object. +Returns the underlying dof basis, which is a vector of `Dof`s, typically +[`LagrangianDofBasis{P,V}`](@ref) for FE using point evaluation based DoFs, or a +[`MomentBasedDofBasis`](@ref) using moment (face integral) based DoFs. """ function get_dof_basis(reffe::ReferenceFE) @abstractmethod @@ -84,43 +196,67 @@ end """ Conformity(reffe::ReferenceFE) -> Conformity + +Return the default conformity of `reffe`. """ function Conformity(reffe::ReferenceFE) @abstractmethod end """ - get_face_own_dofs(reffe::ReferenceFE,conf::Conformity) -> Vector{Vector{Int}} + get_face_own_dofs(reffe::ReferenceFE[, conf::Conformity][, d::Int]) -> Vector{Vector{Int}} + +Return a vector containing, for each face of `reffe`'s polytope, the indices of +the DoFs that belong to the interior of the face. This determines which DoFs +will be glued together in the global FE space: DoFs owned by a vertex or edge +shared by several physical polytopes will be glued/unified. Only the interior +owned DoFs (last face, i.e. the polytope itself) are not glued. + +The ownership thus depends on the [`Conformity`](@ref) of the element: a `:L2` +conforming element is an element for which the polytope owns all DoFs such that +none are glued (e.g. for discontinuous Galerkin methods). + +If `conf` is given, the ownership is computed for `conf` and not for `reffe`'s +conformity. If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `reffe`'s polytope. """ -function get_face_own_dofs(reffe::ReferenceFE,conf::Conformity) +function get_face_own_dofs(reffe::ReferenceFE, conf::Conformity) @abstractmethod end -function Conformity(reffe::ReferenceFE,conf::Conformity) +function Conformity(reffe::ReferenceFE, conf::Conformity) conf end -function Conformity(reffe::ReferenceFE,conf::Nothing) +function Conformity(reffe::ReferenceFE, conf::Nothing) Conformity(reffe) end -function Conformity(reffe::ReferenceFE,sym::Symbol) +""" + Conformity(reffe::ReferenceFE, conf::Symbol) + +Return the [`Conformity`](@ref) that `conf` represents if `reffe` implements it, +or throws an `ErrorException` otherwise. + +For example, if `reffe` is a Lagrangian refference FE with `H1Conformity`, +the function would return `L2Conformity()` and `H1Conformity()` for +respectively `conf=:L2` and `:H1` (because H¹ is in L²), but would error on +`conf=:Hcurl`. +""" +function Conformity(reffe::ReferenceFE, sym::Symbol) @abstractmethod end -""" - get_face_own_dofs(reffe::ReferenceFE) -> Vector{Vector{Int}} -""" function get_face_own_dofs(reffe::ReferenceFE) conf = Conformity(reffe) - get_face_own_dofs(reffe,conf) + get_face_own_dofs(reffe, conf) end -function get_face_own_dofs(reffe::ReferenceFE,conf::Nothing) +function get_face_own_dofs(reffe::ReferenceFE, conf::Nothing) get_face_own_dofs(reffe) end -function get_face_own_dofs(reffe::ReferenceFE,conf::L2Conformity) +function get_face_own_dofs(reffe::ReferenceFE, conf::L2Conformity) _get_face_own_dofs_l2(reffe) end @@ -132,47 +268,72 @@ function _get_face_own_dofs_l2(reffe::ReferenceFE) end """ - get_face_own_dofs_permutations(reffe::ReferenceFE,conf::Conformity) -> Vector{Vector{Vector{Int}}} + get_face_own_dofs_permutations( + reffe::ReferenceFE[, conf::Conformity][, d::Integer]) -> Vector{Vector{Vector{Int}}} + +Return, for each `face` of `reffe`'s polytope, the permutation of the DoF indices +(that `face` owns) corresponding to each possible configuration of the `face` +once mapped in the physical space. These permutations correspond to the vertex +permutation returned by +[`get_face_vertex_permutations(get_polytope(reffe))`](@ref get_face_vertex_permutations). + +`conf` can be passed to chose the conformity defining the DoFs ownership to the faces. +If `d` is given, the permutations are only returned for the `d`-dimensional faces. + +In some cases, a valid permutation of the dofs for a corresponding +relabeling of the vertices does not exist (i.e., lagrangian FEs on `QUAD` with +anisotropic order). For these permutations the corresponding vector in +`get_face_own_dofs_permutations(reffe)` has to be filled with the constant value +[`INVALID_PERM`](@ref). """ -function get_face_own_dofs_permutations(reffe::ReferenceFE,conf::Conformity) - face_own_dofs = get_face_own_dofs(reffe,conf) +function get_face_own_dofs_permutations(reffe::ReferenceFE, conf::Conformity) + face_own_dofs = get_face_own_dofs(reffe, conf) _trivial_face_own_dofs_permutations(face_own_dofs) end function _trivial_face_own_dofs_permutations(face_own_dofs) - [ [collect(Int,1:length(dofs)),] for dofs in face_own_dofs ] + [[collect(Int, 1:length(dofs)),] for dofs in face_own_dofs] end -""" - get_face_own_dofs_permutations(reffe::ReferenceFE) -> Vector{Vector{Vector{Int}}} -""" function get_face_own_dofs_permutations(reffe::ReferenceFE) conf = Conformity(reffe) - get_face_own_dofs_permutations(reffe,conf) + get_face_own_dofs_permutations(reffe, conf) end -function get_face_own_dofs_permutations(reffe::ReferenceFE,conf::Nothing) +function get_face_own_dofs_permutations(reffe::ReferenceFE, conf::Nothing) get_face_own_dofs_permutations(reffe) end """ - get_face_dofs(reffe::ReferenceFE) -> Vector{Vector{Int}} + get_face_dofs(reffe::ReferenceFE[, d::Integer]) -> Vector{Vector{Int}} -Returns a vector of vector that, for each face, stores the -dofids in the closure of the face. +Returns a vector of vector that, for each face of `reffe`'s polytope, stores the +ids of the DoF in the closure of the face. The difference with +[`get_face_own_dofs`](@ref) is that this includes DoFs owned by the boundary +faces of each face. + +If `d` is given, the returned vector only contains the data for the +`d`-dimensional faces of `reffe`'s polytope. """ function get_face_dofs(reffe::ReferenceFE) @abstractmethod end function get_dof_to_comp(reffe::ReferenceFE) - fill(0,num_dofs(reffe)) + fill(0, num_dofs(reffe)) end """ + expand_cell_data(id_to_data, cell_to_id) -> cell_to_data + +Takes a (small) vector of possible datas `type_to_data`, and an array +`cell_to_type` of indices in `type_to_data`, and return a cell array +containing the data of each cell. + +See also [`compress_cell_data`](@ref). """ function expand_cell_data(type_to_data, cell_to_type) - CompressedArray(type_to_data,cell_to_type) + CompressedArray(type_to_data, cell_to_type) end function expand_cell_data(type_to_data, cell_to_type::Fill) @@ -180,7 +341,7 @@ function expand_cell_data(type_to_data, cell_to_type::Fill) @assert length(type_to_data) == 1 "Only one reference element expected" @assert cell_to_type.value == 1 "Only one type of reference element expected" data = first(type_to_data) - Fill(data,ncells) + Fill(data, ncells) end function expand_cell_data(type_to_data, cell_to_type::Base.OneTo) @@ -195,12 +356,23 @@ function compress_cell_data(cell_data::AbstractArray) """ end +""" + compress_cell_data(cell_data::CompressedArray) -> id_to_data, cell_to_id + compress_cell_data(cell_data::Fill) -> id_to_data, cell_to_id + +Takes an array, iddentifies the unique values it contains, and returns the +vector of unique values and an array `cell_to_id` same shape as the input, but +containing the index of the data in `id_to_data` instead of the data. +See also [`CompressedArray`](@ref). + +Use [`expand_cell_data`](@ref) to reverse the operation. +""" function compress_cell_data(a::CompressedArray) a.values, a.ptrs end function compress_cell_data(a::Fill) - fill(a.value,1), Fill(1,length(a)) + fill(a.value, 1), Fill(1, length(a)) end function compress_cell_data(a::Vector) @@ -214,35 +386,35 @@ end Test if the methods in the `ReferenceFE` interface are defined for the object `reffe`. """ -function test_reference_fe(reffe::ReferenceFE{D}) where D +function test_reference_fe(reffe::ReferenceFE{D}) where {D} conf = Conformity(reffe) - @test isa(conf,Conformity) - test_reference_fe(reffe,conf) + @test isa(conf, Conformity) + test_reference_fe(reffe, conf) end -function test_reference_fe(reffe::ReferenceFE{D},conf::Conformity) where D +function test_reference_fe(reffe::ReferenceFE{D}, conf::Conformity) where {D} @test D == num_dims(reffe) p = get_polytope(reffe) - @test isa(p,Polytope{D}) + @test isa(p, Polytope{D}) basis = get_prebasis(reffe) - @test isa(basis,AbstractArray{<:Field}) + @test isa(basis, AbstractArray{<:Field}) dofs = get_dof_basis(reffe) - @test isa(dofs,AbstractArray{<:Dof}) - facedofs = get_face_own_dofs(reffe,conf) - @test isa(facedofs,Vector{Vector{Int}}) + @test isa(dofs, AbstractArray{<:Dof}) + facedofs = get_face_own_dofs(reffe, conf) + @test isa(facedofs, Vector{Vector{Int}}) @test length(facedofs) == num_faces(p) - facedofs_perms = get_face_own_dofs_permutations(reffe,conf) - @test isa(facedofs_perms,Vector{Vector{Vector{Int}}}) + facedofs_perms = get_face_own_dofs_permutations(reffe, conf) + @test isa(facedofs_perms, Vector{Vector{Vector{Int}}}) @test length(facedofs_perms) == num_faces(p) facedofs = get_face_dofs(reffe) - @test isa(facedofs,Vector{Vector{Int}}) + @test isa(facedofs, Vector{Vector{Int}}) @test length(facedofs) == num_faces(p) shapefuns = get_shapefuns(reffe) - @test isa(shapefuns,AbstractVector{<:Field}) + @test isa(shapefuns, AbstractVector{<:Field}) ndofs = num_dofs(reffe) - m = evaluate(dofs,basis) - @test ndofs == size(m,1) - @test ndofs == size(m,2) + m = evaluate(dofs, basis) + @test ndofs == size(m, 1) + @test ndofs == size(m, 2) end @@ -254,125 +426,121 @@ const INVALID_PERM = 0 # API """ - num_dims(::Type{<:ReferenceFE{D}}) where D - num_dims(reffe::ReferenceFE{D}) where D + num_dims(::Type{<:ReferenceFE{D}}) + num_dims(reffe::ReferenceFE{D}) Returns `D`. """ num_dims(reffe::ReferenceFE) = num_dims(typeof(reffe)) -num_dims(::Type{<:ReferenceFE{D}}) where D = D +num_dims(::Type{<:ReferenceFE{D}}) where {D} = D """ - num_cell_dims(::Type{<:ReferenceFE{D}}) where D - num_cell_dims(reffe::ReferenceFE{D}) where D + num_cell_dims(::Type{<:ReferenceFE{D}}) + num_cell_dims(reffe::ReferenceFE{D}) Returns `D`. """ num_cell_dims(reffe::ReferenceFE) = num_dims(typeof(reffe)) -num_cell_dims(::Type{<:ReferenceFE{D}}) where D = D +num_cell_dims(::Type{<:ReferenceFE{D}}) where {D} = D """ - num_point_dims(::Type{<:ReferenceFE{D}}) where D - num_point_dims(reffe::ReferenceFE{D}) where D + num_point_dims(::Type{<:ReferenceFE{D}}) + num_point_dims(reffe::ReferenceFE{D}) Returns `D`. """ num_point_dims(reffe::ReferenceFE) = num_dims(typeof(reffe)) -num_point_dims(::Type{<:ReferenceFE{D}}) where D = D +num_point_dims(::Type{<:ReferenceFE{D}}) where {D} = D """ num_faces(reffe::ReferenceFE) - num_faces(reffe::ReferenceFE,d::Integer) + num_faces(reffe::ReferenceFE, d::Integer) + +Number of faces of `reffe`s polytope, counting the polytope itself. + +If `d` is given, only the `d`-dimensional faces are counted. """ num_faces(reffe::ReferenceFE) = num_faces(get_polytope(reffe)) -num_faces(reffe::ReferenceFE,d::Integer) = num_faces(get_polytope(reffe),d) +num_faces(reffe::ReferenceFE, d::Integer) = num_faces(get_polytope(reffe), d) """ num_vertices(reffe::ReferenceFE) + +Number of `0`-faces of `reffe`s polytope. """ num_vertices(reffe::ReferenceFE) = num_vertices(get_polytope(reffe)) """ num_edges(reffe::ReferenceFE) + +Number of `1`-faces of `reffe`s polytope. """ num_edges(reffe::ReferenceFE) = num_edges(get_polytope(reffe)) """ num_facets(reffe::ReferenceFE) + +Number of `(D-1)`-faces of `reffe`s polytope. """ num_facets(reffe::ReferenceFE) = num_facets(get_polytope(reffe)) -""" - get_face_own_dofs(reffe::ReferenceFE,conf::Conformity,d::Integer) -""" -function get_face_own_dofs(reffe::ReferenceFE,conf::Conformity,d::Integer) +function get_face_own_dofs(reffe::ReferenceFE, conf::Conformity, d::Integer) p = get_polytope(reffe) - range = get_dimrange(p,d) - get_face_own_dofs(reffe,conf)[range] + range = get_dimrange(p, d) + get_face_own_dofs(reffe, conf)[range] end -""" - get_face_own_dofs(reffe::ReferenceFE,d::Integer) -""" -function get_face_own_dofs(reffe::ReferenceFE,d::Integer) +function get_face_own_dofs(reffe::ReferenceFE, d::Integer) conf = Conformity(reffe) - get_face_own_dofs(reffe,conf,d) + get_face_own_dofs(reffe, conf, d) end -""" - get_face_dofs(reffe::ReferenceFE,d::Integer) -""" -function get_face_dofs(reffe::ReferenceFE,d::Integer) +function get_face_dofs(reffe::ReferenceFE, d::Integer) p = get_polytope(reffe) - range = get_dimrange(p,d) + range = get_dimrange(p, d) get_face_dofs(reffe)[range] end -""" - get_face_own_dofs_permutations(reffe::ReferenceFE,conf::Conformity,d::Integer) -""" -function get_face_own_dofs_permutations(reffe::ReferenceFE,conf::Conformity,d::Integer) +function get_face_own_dofs_permutations(reffe::ReferenceFE, conf::Conformity, d::Integer) p = get_polytope(reffe) - range = get_dimrange(p,d) - get_face_own_dofs_permutations(reffe,conf)[range] + range = get_dimrange(p, d) + get_face_own_dofs_permutations(reffe, conf)[range] end -""" - get_face_own_dofs_permutations(reffe::ReferenceFE,d::Integer) -""" -function get_face_own_dofs_permutations(reffe::ReferenceFE,d::Integer) +function get_face_own_dofs_permutations(reffe::ReferenceFE, d::Integer) conf = Conformity(reffe) - get_face_own_dofs_permutations(reffe,conf,d) + get_face_own_dofs_permutations(reffe, conf, d) end """ - get_own_dofs_permutations(reffe::ReferenceFE,conf::Conformity) + get_own_dofs_permutations(reffe::ReferenceFE) + get_own_dofs_permutations(reffe::ReferenceFE, conf::Conformity) + +Same as [`get_face_own_dofs_permutations`](@ref), but only return the last +permutations vector, that of DoFs owned by the cell (but not its boundary faces). """ -function get_own_dofs_permutations(reffe::ReferenceFE,conf::Conformity) - n = num_faces(get_polytope(reffe)) - get_face_own_dofs_permutations(reffe,conf)[n] +function get_own_dofs_permutations(reffe::ReferenceFE, conf::Conformity) + last(get_face_own_dofs_permutations(reffe, conf)) end -""" - get_own_dofs_permutations(reffe::ReferenceFE) -""" function get_own_dofs_permutations(reffe::ReferenceFE) conf = Conformity(reffe) - get_own_dofs_permutations(reffe,conf) + get_own_dofs_permutations(reffe, conf) end """ - get_shapefuns(reffe::ReferenceFE) -> Field + get_shapefuns(reffe::ReferenceFE) -Returns the basis of shape functions (i.e. the canonical basis) -associated with the reference FE. The result is encoded as a `Field` object. +Returns the underlying shape function basis, which is an vector of `Field`s, +often a linear combination of a [`PolynomialBasis`](@ref) (the prebasis) and a +change of basis matrix. """ function get_shapefuns(reffe::ReferenceFE) dofs = get_dof_basis(reffe) prebasis = get_prebasis(reffe) - compute_shapefuns(dofs,prebasis) + compute_shapefuns(dofs, prebasis) end """ @@ -386,25 +554,25 @@ It is equivalent to change = inv(evaluate(dofs,prebasis)) linear_combination(change,prebasis) # i.e. transpose(change)*prebasis """ -function compute_shapefuns(dofs,prebasis) - change = inv(evaluate(dofs,prebasis)) - linear_combination(change,prebasis) +function compute_shapefuns(dofs, prebasis) + change = inv(evaluate(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`. +associated with the DoF prebasis `predofs` and the polynonial basis `shapefuns`. It is equivalent to - change = inv(evaluate(predofs,shapefuns)) + change = transpose(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) +function compute_dofs(predofs, shapefuns) + change = transpose(inv(evaluate(predofs, shapefuns))) + linear_combination(change, predofs) end # Concrete implementation @@ -423,7 +591,7 @@ Note that some fields in this `struct` are type unstable deliberately in order t type signature. Don't access them in computationally expensive functions, instead extract the required fields before and pass them to the computationally expensive function. """ -struct GenericRefFE{T,D} <: ReferenceFE{D} +@ahe struct GenericRefFE{T,D} <: ReferenceFE{D} ndofs::Int polytope::Polytope{D} prebasis::AbstractVector{<:Field} @@ -432,18 +600,20 @@ struct GenericRefFE{T,D} <: ReferenceFE{D} metadata face_dofs::Vector{Vector{Int}} shapefuns::AbstractVector{<:Field} + @doc """ GenericRefFE{T}( - ndofs::Int, - polytope::Polytope{D}, - prebasis::AbstractVector{<:Field}, - dofs::AbstractVector{<:Dof}, - conformity::Conformity, - metadata, - face_dofs::Vector{Vector{Int}}, - shapefuns::AbstractVector{<:Field}=compute_shapefuns(dofs,prebasis)) where {T,D} - - Constructs a `GenericRefFE` object with the provided data. + ndofs::Int, + polytope::Polytope{D}, + prebasis::AbstractVector{<:Field}, + dofs::AbstractVector{<:Dof}, + conformity::Conformity, + metadata, + face_dofs::Vector{Vector{Int}}, + shapefuns::AbstractVector{<:Field}=compute_shapefuns(dofs,prebasis) + ) where {T,D} + + Constructor using a DoF basis and function space pre-basis. """ function GenericRefFE{T}( ndofs::Int, @@ -453,7 +623,7 @@ struct GenericRefFE{T,D} <: ReferenceFE{D} conformity::Conformity, metadata, face_dofs::Vector{Vector{Int}}, - shapefuns::AbstractVector{<:Field}=compute_shapefuns(dofs,prebasis)) where {T,D} + shapefuns::AbstractVector{<:Field}=compute_shapefuns(dofs, prebasis)) where {T,D} new{T,D}( ndofs, @@ -467,17 +637,18 @@ struct GenericRefFE{T,D} <: ReferenceFE{D} 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. + 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} + + Constructor using shape function basis and a DoF pre-basis. """ function GenericRefFE{T}( ndofs::Int, @@ -487,7 +658,7 @@ struct GenericRefFE{T,D} <: ReferenceFE{D} metadata, face_dofs::Vector{Vector{Int}}, shapefuns::AbstractVector{<:Field}, - dofs::AbstractVector{<:Dof}=compute_dofs(predofs,shapefuns)) where {T,D} + dofs::AbstractVector{<:Dof}=compute_dofs(predofs, shapefuns)) where {T,D} new{T,D}( ndofs, @@ -497,10 +668,14 @@ struct GenericRefFE{T,D} <: ReferenceFE{D} conformity, metadata, face_dofs, - linear_combination(Eye{Int}(ndofs),shapefuns)) # Trick to be able to eval dofs af shapefuns in physical space + # Trick to be able to eval dofs af shapefuns in physical space + # cf /test/FESpacesTests/PhysicalFESpacesTests.jl + linear_combination(Eye{Int}(ndofs), shapefuns)) end end +get_name(::Type{<:GenericRefFE{Name}}) where Name = Name() + num_dofs(reffe::GenericRefFE) = reffe.ndofs get_polytope(reffe::GenericRefFE) = reffe.polytope @@ -515,4 +690,5 @@ get_face_dofs(reffe::GenericRefFE) = reffe.face_dofs get_shapefuns(reffe::GenericRefFE) = reffe.shapefuns -get_metadata(reffe::GenericRefFE) = reffe.metadata \ No newline at end of file +get_metadata(reffe::GenericRefFE) = reffe.metadata + diff --git a/src/ReferenceFEs/ReferenceFEs.jl b/src/ReferenceFEs/ReferenceFEs.jl index 592c0942d..ad3ddc029 100644 --- a/src/ReferenceFEs/ReferenceFEs.jl +++ b/src/ReferenceFEs/ReferenceFEs.jl @@ -1,7 +1,10 @@ """ -The exported names are -$(EXPORTS) +$(public_names_in_md(@__MODULE__; change_link=Dict( + :H1Conformity => "GradConformity", + :nedelec1 => "nedelec", + :modal_serendipity => "modal_lagrangian", +))) """ module ReferenceFEs @@ -11,6 +14,7 @@ using LinearAlgebra using StaticArrays using Combinatorics using FillArrays +using AutoHashEquals: @auto_hash_equals as @ahe using ..Gridap using Gridap.Helpers @@ -19,7 +23,7 @@ using Gridap.TensorValues using Gridap.Fields using Gridap.Polynomials -using Gridap.Polynomials: _q_filter, _s_filter_mc0 +using Gridap.Polynomials: _q_filter, _ser_filter using Gridap.Polynomials: _compute_filter_mask using Gridap.Polynomials: _define_terms, _sort_by_nfaces! @@ -33,7 +37,6 @@ 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 @@ -96,13 +99,16 @@ export HEX_AXIS export TET_AXIS export INVALID_PERM +export Pushforward +export Pullback +export IdentityPiolaMap +export CoVariantPiolaMap export ContraVariantPiolaMap export Dof export get_nodes export get_face_moments export get_face_nodes_dofs -export get_nodes export evaluate! export return_cache export return_type @@ -112,10 +118,10 @@ export test_dof_array export ReferenceFE export ReferenceFEName export GenericRefFE +export get_name export get_polytope export get_prebasis export get_dof_basis -export Conformity export get_face_own_dofs export get_face_own_dofs_permutations export get_face_dofs @@ -151,6 +157,10 @@ export is_Q export is_P export is_S +export has_geometric_decomposition +export get_face_own_funs +export get_facet_flux_sign_flip + export MomentBasedDofBasis export get_face_own_nodes export get_face_nodes @@ -182,23 +192,35 @@ export BDMRefFE export NedelecRefFE export BezierRefFE export ModalC0RefFE +export ModalScalarRefFE +export CrouzeixRaviartRefFE export BubbleRefFE export Lagrangian -export DivConforming export RaviartThomas export BDM export Nedelec export Bezier export ModalC0 +export ModalScalar +export CrouzeixRaviart +export Serendipity +#export HellanHerrmannJhonson export Bubble export lagrangian export raviart_thomas export bdm export nedelec +export nedelec1 +export nedelec2 export bezier export modalC0 +export modal_lagrangian +export modal_serendipity +export crouzeix_raviart +export serendipity +#export hhj export bubble export Quadrature @@ -228,6 +250,12 @@ include("LagrangianDofBases.jl") include("ReferenceFEInterfaces.jl") +include("GeometricDecompositions.jl") + +include("ExteriorCalculusRefFEs.jl") + +include("Pullbacks.jl") + include("LagrangianRefFEs.jl") include("CLagrangianRefFEs.jl") @@ -240,6 +268,8 @@ include("CDLagrangianRefFEs.jl") include("Quadratures.jl") +include("SegmentQuadratures.jl") + include("TensorProductQuadratures.jl") include("DuffyQuadratures.jl") @@ -248,12 +278,22 @@ include("StrangQuadratures.jl") include("XiaoGimbutasQuadratures.jl") +include("PolytopalQuadratures.jl") + +include("MomentBasedReferenceFEs.jl") + include("RaviartThomasRefFEs.jl") include("BDMRefFEs.jl") include("NedelecRefFEs.jl") +include("ModalScalarRefFEs.jl") + +include("CrouzeixRaviartRefFEs.jl") + +#include("HHJRefFEs.jl") + include("MockDofs.jl") include("BezierRefFEs.jl") @@ -262,8 +302,4 @@ include("ModalC0RefFEs.jl") include("BubbleRefFEs.jl") -include("LinearCombinationDofVectors.jl") - -include("PolytopalQuadratures.jl") - end # module diff --git a/src/ReferenceFEs/SegmentQuadratures.jl b/src/ReferenceFEs/SegmentQuadratures.jl new file mode 100644 index 000000000..16ec5f4e2 --- /dev/null +++ b/src/ReferenceFEs/SegmentQuadratures.jl @@ -0,0 +1,139 @@ + +# Gauss-Jacobi and Gauss-Legendre quadratures + +""" + gauss_jacobi_quadrature(order,alpha,beta;T::Type{<:AbstractFloat}=Float64,a=zero(T),b=one(T)) -> x, w + +Computes the Gauss-Jacobi quadrature rule of order `order` on the reference segment `[a,b]` +with parameters `alpha` and `beta`. Gauss-Legendre quadratures are obtained for `alpha = beta = 0`. + +Uses the package `FastGaussQuadratures.jl`, which only allows Float64. If any other type +is used, the Float64 values are converted to the requested type. +""" +function gauss_jacobi_quadrature( + order,alpha,beta;T::Type{<:AbstractFloat}=Float64,a=zero(T),b=one(T) +) + n = _npoints_from_order(order) + if iszero(alpha) && iszero(beta) + x, w = gausslegendre(n) + else + x, w = gaussjacobi(n,alpha,beta) + end + _map_segment(a,b,x,w;T) +end + +""" + gauss_legendre_quadrature(order;T::Type{<:AbstractFloat}=Float64,a=zero(T),b=one(T)) -> x, w + +Computes the Gauss-Legendre quadrature rule of order `order` on the reference segment `[a,b]`. +Uses the package `QuadGK.jl`, which allows for arbitrary precision natively. +""" +function gauss_legendre_quadrature( + order;T::Type{<:AbstractFloat}=Float64,a=zero(T),b=one(T) +) + n = _npoints_from_order(order) + x, w = gauss(T, n, a, b) + x, w +end + +# Utils + +# Transforms a 1-D quadrature from `[-1,1]` to `[a,b]`, with `a= 150 +function sample_quasi_chebyshev_3(i, n) + x = (2 * i - 1) // (2 * n) + + b = 2733 // 1000 # Magic constant + if x < 1 / 2 + x = (2 * (2 - b) * x + b) * x^2 + else + x = 1 - (2 * (2 - b) * (1 - x) + b) * (1 - x)^2 + end + + x +end diff --git a/src/ReferenceFEs/SerendipityRefFEs.jl b/src/ReferenceFEs/SerendipityRefFEs.jl index 935a72c0e..98b10c6a5 100644 --- a/src/ReferenceFEs/SerendipityRefFEs.jl +++ b/src/ReferenceFEs/SerendipityRefFEs.jl @@ -1,12 +1,26 @@ """ - SerendipityRefFE(::Type{T},p::Polytope,order::Int) where T - SerendipityRefFE(::Type{T},p::Polytope,orders::Tuple) where T + struct Serendipity <: ReferenceFEName +""" +struct Serendipity <: ReferenceFEName end + +""" + const serendipity = Serendipity() + +Singleton of the [`Serendipity`](@ref) reference FE name. +""" +const serendipity = Serendipity() + +""" + SerendipityRefFE(::Type{T}, p::Polytope, order::Int) + SerendipityRefFE(::Type{T}, p::Polytope, orders::Tuple) + +Return a Lagrangian reference FE whose underlying approximation space is the +serendipity polynomial space 𝕊r of order `order`. Implemented on n-cubes with +homogneous order. -Returns an instance of `LagrangianRefFE`, whose underlying approximation space -is the serendipity space of order `order`. Implemented for order from 1 to 4. The type of the polytope `p` has to implement all the queries detailed in the -constructor [`LagrangianRefFE(::Type{T},p::Polytope{D},orders) where {T,D}`](@ref). +constructor [`LagrangianRefFE(::Type{T}, p::Polytope{D}, orders) where {T,D}`](@ref). # Examples @@ -23,22 +37,31 @@ println( num_dofs(reffe) ) ``` """ -function SerendipityRefFE(::Type{T},p::Polytope,order::Int) where T +function SerendipityRefFE(::Type{T},p::Polytope,order::Int; + poly_type=Monomial) where T + @assert is_n_cube(p) "Polytope not compatible with serendipity elements" if order > 0 - sp = SerendipityPolytope(p) + sp = SerendipityPolytope(p) else sp = p end - LagrangianRefFE(T,sp,order) + LagrangianRefFE(T,sp,order; poly_type) end -function SerendipityRefFE(::Type{T},p::Polytope,orders::Tuple) where T +function SerendipityRefFE(::Type{T},p::Polytope,orders::Tuple; + poly_type=Monomial) where T + order = first(orders) - @assert all( orders .== order ) "Anisotropic serentopity FEs not allowed" - SerendipityRefFE(T,p,order) + @assert all( orders .== order ) "Serendipity FEs must be isotropic, got orders $orders." + SerendipityRefFE(T,p,order; poly_type) +end + +function ReferenceFE(p::Polytope,::Serendipity,::Type{T},order;kwargs...) where T + SerendipityRefFE(T,p,order;kwargs...) end + # Helper private type struct SerendipityPolytope{D,P} <: Polytope{D} hex::P @@ -87,12 +110,14 @@ get_extrusion(p::SerendipityPolytope{D}) where D = Point(tfill(HEX_AXIS,Val{D}() # Implemented polytope interface for LagrangianRefFEs -function _s_filter(e,order) - sum( [ i for i in e if i>1 ] ) <= order +function compute_monomial_basis(::Type{T},p::SerendipityPolytope{D},orders) where {T,D} + MonomialBasis(Val(D),T,orders,_ser_filter) end -function compute_monomial_basis(::Type{T},p::SerendipityPolytope{D},orders) where {T,D} - MonomialBasis{D}(T,orders,_s_filter) +function compute_poly_basis(::Type{T},p::SerendipityPolytope{D},orders,poly_type) where {T,D} + FEEC_poly_basis + r = iszero(D) ? 0 : first(orders) + FEEC_poly_basis(Val(D),T,r,0,:S,poly_type) # SᵣΛ⁰(□ᴰ) end function compute_own_nodes(p::SerendipityPolytope{0},orders) diff --git a/src/ReferenceFEs/StrangQuadratures.jl b/src/ReferenceFEs/StrangQuadratures.jl index dc3054fe9..7e17d4552 100644 --- a/src/ReferenceFEs/StrangQuadratures.jl +++ b/src/ReferenceFEs/StrangQuadratures.jl @@ -1,5 +1,18 @@ +""" + struct Strang <: QuadratureName + +Strang quadrature rule for simplices. + +# Constructor: + + Quadrature(p::Polytope,strang,degree::Integer;T::Type{<:AbstractFloat}=Float64) +""" struct Strang <: QuadratureName end + +""" + const strang = Strang() +""" const strang = Strang() function Quadrature(p::Polytope,::Strang,degree::Integer;T::Type{<:AbstractFloat}=Float64) diff --git a/src/ReferenceFEs/TensorProductQuadratures.jl b/src/ReferenceFEs/TensorProductQuadratures.jl index b88aa12bc..c5c4d271c 100644 --- a/src/ReferenceFEs/TensorProductQuadratures.jl +++ b/src/ReferenceFEs/TensorProductQuadratures.jl @@ -1,71 +1,90 @@ +""" + struct TensorProduct <: QuadratureName + +Tensor product quadrature rule for n-cubes, obtained as the +tensor product of 1d Gauss-Legendre quadratures. + +# Constructor: + + Quadrature(p::Polytope{D}, tensor_product, degrees::Integer; T::Type=Float64) + Quadrature(p::Polytope{D}, tensor_product, degrees::NTuple{D,Integer}; T::Type=Float64) +""" struct TensorProduct <: QuadratureName end +""" + const tensor_product = TensorProduct() +""" const tensor_product = TensorProduct() -function Quadrature(p::Polytope,::TensorProduct,degrees;T::Type{<:AbstractFloat}=Float64) - @assert is_n_cube(p) """\n - Tensor product quadrature rule only for n-cubes. - """ - @assert length(degrees) == num_dims(p) - _tensor_product_legendre(degrees;T=T) +function Quadrature(p::Polytope,::TensorProduct,degrees; T::Type=Float64) + @assert is_n_cube(p) "Tensor product quadrature rule only for n-cubes." + D = num_dims(p) + if iszero(D) + coords, weights = [zero(Point{0,T})], [one(T)] + else + @assert length(degrees) == D + quad_1d = [ gauss_legendre_quadrature(degree;T) for degree in degrees ] + coords_1d, weights_1d = map(first, quad_1d), map(last, quad_1d) + coords, weights = _tensor_product(coords_1d, weights_1d) + end + GenericQuadrature(coords,weights,"Tensor product of 1d Gauss-Legendre quadratures of degrees $degrees") end -function Quadrature(p::Polytope,name::TensorProduct,degree::Integer;T::Type{<:AbstractFloat}=Float64) +function Quadrature(p::Polytope,name::TensorProduct,degree::Integer;kwargs...) degrees = ntuple(i->degree,Val(num_dims(p))) - Quadrature(p,name,degrees,T=T) + Quadrature(p,name,degrees;kwargs...) end -# Low level constructor +function Quadrature(p::Polytope,::TensorProduct,quadratures::Vector{<:Quadrature{1}}) + @assert is_n_cube(p) "Tensor product quadrature rule only for n-cubes." + D = num_dims(p) + @assert length(quadratures) == D -function _tensor_product_legendre(degrees;T::Type{<:AbstractFloat}=Float64) - D = length(degrees) - npoints = [ ceil(Int,(degrees[i]+1.0)/2.0) for i in 1:D ] - quads = [ gauss(T, npoints[i]) for i in 1:D ] - for i in 1:D - quads[i][1] .+= 1; - quads[i][1] .*= 1.0/2.0 - quads[i][2] .*= 1.0/2.0 - end - (coords, weights) = _tensor_product(Point{D,T},quads,npoints) - GenericQuadrature(coords,weights,"Tensor product of 1d Gauss-Legendre quadratures of degrees $degrees") + coords_1d = map(q -> map(xi -> xi[1], get_coordinates(q)), quadratures) + weights_1d = map(get_weights, quadratures) + coords, weights = _tensor_product(coords_1d, weights_1d) + + names_1d = join(map(get_name, quadratures)," \n - ") + GenericQuadrature(coords,weights,"Tensor product of 1d quadratures given by: \n "*names_1d) end # Helpers -function _tensor_product(::Type{Point{D,T}},quads,npoints) where {D,T} - @assert length(quads) == D - @assert length(npoints) == D - cis = CartesianIndices(tuple(npoints...)) - n = prod(npoints) - coords = Vector{Point{D,T}}(undef,n) - weights = Vector{T}(undef,n) - _tensor_product!(quads,coords,weights,cis) - (coords, weights) -end +function _tensor_product( + d_to_xs::Vector{Vector{T}}, + d_to_ws::Vector{Vector{W}} +) where {T,W} + D = length(d_to_xs) + d_to_n = map(length, d_to_xs) + cis = CartesianIndices(tuple(d_to_n...)) -function _tensor_product(::Type{Point{0,T}},quads,npoints) where T - coords = [zero(Point{0,T})] - weights = [one(T)] - coords, weights + n = prod(d_to_n) + xs = Vector{Point{D,T}}(undef,n) + ws = Vector{W}(undef,n) + _tensor_product!(xs,ws,d_to_xs,d_to_ws,cis) + return xs, ws end -function _tensor_product!(quads,coords,weights,cis) - p = zero(Mutable(eltype(coords))) - T = eltype(weights) - D = length(p) - lis = LinearIndices(cis) +function _tensor_product!(xs,ws,d_to_xs,d_to_ws,cis) + P = eltype(xs) + T = eltype(P) + W = eltype(ws) + D = length(P) + + k = 1 + z = zero(T) + p = zeros(T,D) for ci in cis - p[:] = 0.0 - w = one(T) + fill!(p,z) + w = one(W) for d in 1:D - xi = quads[d][1][ci[d]] - wi = quads[d][2][ci[d]] - p[d] = xi - w *= wi + p[d] = d_to_xs[d][ci[d]] + w *= d_to_ws[d][ci[d]] end - li = lis[ci] - coords[li] = p - weights[li] = w + xs[k] = p + ws[k] = w + k += 1 end + return xs, ws end diff --git a/src/ReferenceFEs/XiaoGimbutasQuadratures.jl b/src/ReferenceFEs/XiaoGimbutasQuadratures.jl index 7fd185d28..a91b4821d 100644 --- a/src/ReferenceFEs/XiaoGimbutasQuadratures.jl +++ b/src/ReferenceFEs/XiaoGimbutasQuadratures.jl @@ -1,18 +1,33 @@ +""" + struct XiaoGimbutas <: QuadratureName + +Xiao-Gimbutas symmetric quadrature rule for simplices. + +# Constructor: + + Quadrature(p::Polytope, xiao_gimbutas, degree::Integer; T::Type{<:AbstractFloat}=Float64) + +# Reference: + + `A numerical algorithm for the construction of efficient quadrature rules in two and higher dimensions`, + Hong Xiao, Zydrunas Gimbutas, Computers & Mathematics with Applications, (2010), + DOI : https://doi.org/10.1016/j.camwa.2009.10.027 + +Adapted from: https://github.com/FEniCS/basix/blob/main/cpp/basix/quadrature.cpp +""" struct XiaoGimbutas <: QuadratureName end + +""" + const xiao_gimbutas = XiaoGimbutas() +""" const xiao_gimbutas = XiaoGimbutas() -# Reference: -# `A numerical algorithm for the construction of efficient quadrature rules in two and higher dimensions`, -# Hong Xiao, Zydrunas Gimbutas, Computers & Mathematics with Applications, (2010) -# DOI : https://doi.org/10.1016/j.camwa.2009.10.027 -# Adapted from: -# https://github.com/FEniCS/basix/blob/main/cpp/basix/quadrature.cpp function Quadrature( p::Polytope,::XiaoGimbutas,degree::Integer;T::Type{<:AbstractFloat}=Float64 ) msg = """\n - `strang` quadrature rule only available for simplices. + `xiao_gimbutas` quadrature rule only available for simplices. Use `tensor_product` for n-cubes. """ @assert is_simplex(p) msg @@ -24,7 +39,7 @@ function Quadrature( x, w = _xiaogimbutas_quad_tet(degree) else msg = """\n - `strang` quadrature rule only available for tris and tets. + `xiao_gimbutas` quadrature rule only available for tris and tets. Use `duffy` for other simplices. """ @unreachable msg @@ -60,10 +75,10 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 3) x = [ - VectorValue(0.4459484909159649, 0.4459484909159649), + VectorValue(0.4459484909159649, 0.4459484909159649), VectorValue(0.09157621350977085, 0.09157621350977085), VectorValue(0.4459484909159649, 0.10810301816807022), - VectorValue(0.09157621350977085, 0.8168475729804583), + VectorValue(0.09157621350977085, 0.8168475729804583), VectorValue(0.10810301816807022, 0.4459484909159649), VectorValue(0.8168475729804583, 0.09157621350977085) ] @@ -75,7 +90,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.4459484909159649, 0.4459484909159649), VectorValue(0.09157621350977085, 0.09157621350977085), VectorValue(0.4459484909159649, 0.10810301816807022), - VectorValue(0.09157621350977085, 0.8168475729804583), + VectorValue(0.09157621350977085, 0.8168475729804583), VectorValue(0.10810301816807022, 0.4459484909159649), VectorValue(0.8168475729804583, 0.09157621350977085) ] @@ -84,11 +99,11 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 5) x = [ - VectorValue(0.3333333333333333, 0.3333333333333333), + VectorValue(0.3333333333333333, 0.3333333333333333), VectorValue(0.1012865073234564, 0.1012865073234564), VectorValue(0.47014206410511505, 0.47014206410511505), - VectorValue(0.1012865073234564, 0.7974269853530872), - VectorValue(0.47014206410511505,0.05971587178976989), + VectorValue(0.1012865073234564, 0.7974269853530872), + VectorValue(0.47014206410511505,0.05971587178976989), VectorValue(0.7974269853530872, 0.1012865073234564), VectorValue(0.05971587178976989, 0.47014206410511505) ] @@ -99,20 +114,20 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 6) x = [ - VectorValue(0.21942998254978302, 0.21942998254978302), - VectorValue(0.48013796411221504, 0.48013796411221504), + VectorValue(0.21942998254978302, 0.21942998254978302), + VectorValue(0.48013796411221504, 0.48013796411221504), VectorValue(0.21942998254978302, 0.561140034900434), - - VectorValue(0.48013796411221504, 0.039724071775569914), - VectorValue(0.561140034900434, 0.21942998254978302), + + VectorValue(0.48013796411221504, 0.039724071775569914), + VectorValue(0.561140034900434, 0.21942998254978302), VectorValue(0.039724071775569914, 0.48013796411221504), - - VectorValue(0.019371724361240805, 0.14161901592396814), - VectorValue(0.8390092597147911, 0.019371724361240805), + + VectorValue(0.019371724361240805, 0.14161901592396814), + VectorValue(0.8390092597147911, 0.019371724361240805), VectorValue(0.14161901592396814, 0.8390092597147911), - - VectorValue(0.14161901592396814, 0.019371724361240805), - VectorValue(0.8390092597147911, 0.14161901592396814), + + VectorValue(0.14161901592396814, 0.019371724361240805), + VectorValue(0.8390092597147911, 0.14161901592396814), VectorValue(0.019371724361240805, 0.8390092597147911) ] w = [0.08566656207649052, 0.04036554479651549, 0.08566656207649052, @@ -123,20 +138,20 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 7) x = [ - VectorValue(0.47319565368925104, 0.47319565368925104), - VectorValue(0.057797640054506494,0.057797640054506494), + VectorValue(0.47319565368925104, 0.47319565368925104), + VectorValue(0.057797640054506494,0.057797640054506494), VectorValue(0.24166360639724743, 0.24166360639724743), - VectorValue(0.47319565368925104, 0.05360869262149792), - VectorValue(0.057797640054506494,0.884404719890987), + VectorValue(0.47319565368925104, 0.05360869262149792), + VectorValue(0.057797640054506494,0.884404719890987), VectorValue(0.24166360639724743, 0.5166727872055051), - VectorValue(0.05360869262149792, 0.47319565368925104), - VectorValue(0.884404719890987,0.057797640054506494), + VectorValue(0.05360869262149792, 0.47319565368925104), + VectorValue(0.884404719890987,0.057797640054506494), VectorValue(0.5166727872055051, 0.24166360639724743), - VectorValue(0.046971206130085534, 0.2593390118657857), - VectorValue(0.6936897820041288,0.046971206130085534), + VectorValue(0.046971206130085534, 0.2593390118657857), + VectorValue(0.6936897820041288,0.046971206130085534), VectorValue(0.2593390118657857, 0.6936897820041288), - VectorValue(0.2593390118657857, 0.046971206130085534), - VectorValue(0.6936897820041288, 0.2593390118657857), + VectorValue(0.2593390118657857, 0.046971206130085534), + VectorValue(0.6936897820041288, 0.2593390118657857), VectorValue(0.046971206130085534, 0.6936897820041288) ] w = [ @@ -149,20 +164,20 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 8) x = [ - VectorValue(0.3333333333333333, 0.3333333333333333), - VectorValue(0.17056930775176027,0.17056930775176027), + VectorValue(0.3333333333333333, 0.3333333333333333), + VectorValue(0.17056930775176027,0.17056930775176027), VectorValue(0.4592925882927231, 0.4592925882927231), - VectorValue(0.05054722831703107, 0.05054722831703107), - VectorValue(0.17056930775176027,0.6588613844964795), + VectorValue(0.05054722831703107, 0.05054722831703107), + VectorValue(0.17056930775176027,0.6588613844964795), VectorValue(0.4592925882927231, 0.08141482341455375), - VectorValue(0.05054722831703107, 0.8989055433659379), - VectorValue(0.6588613844964795,0.17056930775176027), + VectorValue(0.05054722831703107, 0.8989055433659379), + VectorValue(0.6588613844964795,0.17056930775176027), VectorValue(0.08141482341455375, 0.4592925882927231), - VectorValue(0.8989055433659379, 0.05054722831703107), - VectorValue(0.008394777409957675,0.26311282963463806), + VectorValue(0.8989055433659379, 0.05054722831703107), + VectorValue(0.008394777409957675,0.26311282963463806), VectorValue(0.7284923929554044, 0.008394777409957675), - VectorValue(0.26311282963463806, 0.7284923929554044), - VectorValue(0.26311282963463806,0.008394777409957675), + VectorValue(0.26311282963463806, 0.7284923929554044), + VectorValue(0.26311282963463806,0.008394777409957675), VectorValue(0.7284923929554044, 0.26311282963463806), VectorValue(0.008394777409957675, 0.7284923929554044) ] @@ -177,23 +192,23 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 9) x = [ - VectorValue(0.3333333333333333, 0.3333333333333333), - VectorValue(0.4896825191987376,0.4896825191987376), + VectorValue(0.3333333333333333, 0.3333333333333333), + VectorValue(0.4896825191987376,0.4896825191987376), VectorValue(0.1882035356190328, 0.1882035356190328), - VectorValue(0.43708959149293664, 0.43708959149293664), - VectorValue(0.04472951339445275,0.04472951339445275), + VectorValue(0.43708959149293664, 0.43708959149293664), + VectorValue(0.04472951339445275,0.04472951339445275), VectorValue(0.4896825191987376, 0.02063496160252476), - VectorValue(0.1882035356190328, 0.6235929287619344), - VectorValue(0.43708959149293664,0.12582081701412673), + VectorValue(0.1882035356190328, 0.6235929287619344), + VectorValue(0.43708959149293664,0.12582081701412673), VectorValue(0.04472951339445275, 0.9105409732110945), - VectorValue(0.02063496160252476, 0.4896825191987376), - VectorValue(0.6235929287619344,0.1882035356190328), + VectorValue(0.02063496160252476, 0.4896825191987376), + VectorValue(0.6235929287619344,0.1882035356190328), VectorValue(0.12582081701412673, 0.43708959149293664), - VectorValue(0.9105409732110945, 0.04472951339445275), - VectorValue(0.0368384120547363,0.2219629891607657), + VectorValue(0.9105409732110945, 0.04472951339445275), + VectorValue(0.0368384120547363,0.2219629891607657), VectorValue(0.741198598784498, 0.0368384120547363), - VectorValue(0.2219629891607657, 0.741198598784498), - VectorValue(0.2219629891607657,0.0368384120547363), + VectorValue(0.2219629891607657, 0.741198598784498), + VectorValue(0.2219629891607657,0.0368384120547363), VectorValue(0.741198598784498, 0.2219629891607657), VectorValue(0.0368384120547363, 0.741198598784498) ] @@ -207,29 +222,29 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 10) x = [ - VectorValue(0.3333333333333333, 0.3333333333333333), - VectorValue(0.4951734598011705,0.4951734598011705), + VectorValue(0.3333333333333333, 0.3333333333333333), + VectorValue(0.4951734598011705,0.4951734598011705), VectorValue(0.019139415242841296, 0.019139415242841296), - VectorValue(0.18448501268524653, 0.18448501268524653), - VectorValue(0.42823482094371884,0.42823482094371884), + VectorValue(0.18448501268524653, 0.18448501268524653), + VectorValue(0.42823482094371884,0.42823482094371884), VectorValue(0.4951734598011705, 0.009653080397658997), - VectorValue(0.019139415242841296, 0.9617211695143174), - VectorValue(0.18448501268524653,0.6310299746295069), + VectorValue(0.019139415242841296, 0.9617211695143174), + VectorValue(0.18448501268524653,0.6310299746295069), VectorValue(0.42823482094371884, 0.14353035811256232), - VectorValue(0.009653080397658997, 0.4951734598011705), - VectorValue(0.9617211695143174,0.019139415242841296), + VectorValue(0.009653080397658997, 0.4951734598011705), + VectorValue(0.9617211695143174,0.019139415242841296), VectorValue(0.6310299746295069, 0.18448501268524653), - VectorValue(0.14353035811256232, 0.42823482094371884), - VectorValue(0.03472362048232748,0.13373475510086913), + VectorValue(0.14353035811256232, 0.42823482094371884), + VectorValue(0.03472362048232748,0.13373475510086913), VectorValue(0.03758272734119169, 0.3266931362813369), - VectorValue(0.8315416244168035, 0.03472362048232748), - VectorValue(0.6357241363774714,0.03758272734119169), + VectorValue(0.8315416244168035, 0.03472362048232748), + VectorValue(0.6357241363774714,0.03758272734119169), VectorValue(0.13373475510086913, 0.8315416244168035), - VectorValue(0.3266931362813369, 0.6357241363774714), - VectorValue(0.13373475510086913,0.03472362048232748), + VectorValue(0.3266931362813369, 0.6357241363774714), + VectorValue(0.13373475510086913,0.03472362048232748), VectorValue(0.3266931362813369, 0.03758272734119169), - VectorValue(0.8315416244168035, 0.13373475510086913), - VectorValue(0.6357241363774714,0.3266931362813369), + VectorValue(0.8315416244168035, 0.13373475510086913), + VectorValue(0.6357241363774714,0.3266931362813369), VectorValue(0.03472362048232748, 0.8315416244168035), VectorValue(0.03758272734119169, 0.6357241363774714) ] @@ -245,32 +260,32 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 11) x = [ - VectorValue(0.3333333333333333, 0.3333333333333333), - VectorValue(0.030846895635588123,0.030846895635588123), + VectorValue(0.3333333333333333, 0.3333333333333333), + VectorValue(0.030846895635588123,0.030846895635588123), VectorValue(0.49878016517846074, 0.49878016517846074), - VectorValue(0.11320782728669404, 0.11320782728669404), - VectorValue(0.4366550163931761,0.4366550163931761), + VectorValue(0.11320782728669404, 0.11320782728669404), + VectorValue(0.4366550163931761,0.4366550163931761), VectorValue(0.21448345861926937, 0.21448345861926937), - VectorValue(0.030846895635588123, 0.9383062087288238), - VectorValue(0.49878016517846074,0.0024396696430785125), + VectorValue(0.030846895635588123, 0.9383062087288238), + VectorValue(0.49878016517846074,0.0024396696430785125), VectorValue(0.11320782728669404, 0.7735843454266119), - VectorValue(0.4366550163931761, 0.12668996721364778), - VectorValue(0.21448345861926937,0.5710330827614613), + VectorValue(0.4366550163931761, 0.12668996721364778), + VectorValue(0.21448345861926937,0.5710330827614613), VectorValue(0.9383062087288238, 0.030846895635588123), - VectorValue(0.0024396696430785125, 0.49878016517846074), - VectorValue(0.7735843454266119,0.11320782728669404), + VectorValue(0.0024396696430785125, 0.49878016517846074), + VectorValue(0.7735843454266119,0.11320782728669404), VectorValue(0.12668996721364778, 0.4366550163931761), - VectorValue(0.5710330827614613, 0.21448345861926937), - VectorValue(0.014366662569555624,0.1593036198376935), + VectorValue(0.5710330827614613, 0.21448345861926937), + VectorValue(0.014366662569555624,0.1593036198376935), VectorValue(0.04766406697215078, 0.31063121631346313), - VectorValue(0.8263297175927509, 0.014366662569555624), - VectorValue(0.6417047167143861,0.04766406697215078), + VectorValue(0.8263297175927509, 0.014366662569555624), + VectorValue(0.6417047167143861,0.04766406697215078), VectorValue(0.1593036198376935, 0.8263297175927509), - VectorValue(0.31063121631346313, 0.6417047167143861), - VectorValue(0.1593036198376935,0.014366662569555624), + VectorValue(0.31063121631346313, 0.6417047167143861), + VectorValue(0.1593036198376935,0.014366662569555624), VectorValue(0.31063121631346313, 0.04766406697215078), - VectorValue(0.8263297175927509, 0.1593036198376935), - VectorValue(0.6417047167143861,0.31063121631346313), + VectorValue(0.8263297175927509, 0.1593036198376935), + VectorValue(0.6417047167143861,0.31063121631346313), VectorValue(0.014366662569555624, 0.8263297175927509), VectorValue(0.04766406697215078, 0.6417047167143861) ]; @@ -288,38 +303,38 @@ function _xiaogimbutas_quad_tri(degree) return x, w elseif (degree == 12) x = [ - VectorValue(0.27146250701492614, 0.27146250701492614), - VectorValue(0.10925782765935432,0.10925782765935432), + VectorValue(0.27146250701492614, 0.27146250701492614), + VectorValue(0.10925782765935432,0.10925782765935432), VectorValue(0.4401116486585931, 0.4401116486585931), - VectorValue(0.4882037509455415, 0.4882037509455415), - VectorValue(0.02464636343633564,0.02464636343633564), + VectorValue(0.4882037509455415, 0.4882037509455415), + VectorValue(0.02464636343633564,0.02464636343633564), VectorValue(0.27146250701492614, 0.45707498597014773), - VectorValue(0.10925782765935432, 0.7814843446812914), - VectorValue(0.4401116486585931,0.11977670268281382), + VectorValue(0.10925782765935432, 0.7814843446812914), + VectorValue(0.4401116486585931,0.11977670268281382), VectorValue(0.4882037509455415, 0.02359249810891695), - VectorValue(0.02464636343633564, 0.9507072731273287), - VectorValue(0.45707498597014773,0.27146250701492614), + VectorValue(0.02464636343633564, 0.9507072731273287), + VectorValue(0.45707498597014773,0.27146250701492614), VectorValue(0.7814843446812914, 0.10925782765935432), - VectorValue(0.11977670268281382, 0.4401116486585931), - VectorValue(0.02359249810891695,0.4882037509455415), + VectorValue(0.11977670268281382, 0.4401116486585931), + VectorValue(0.02359249810891695,0.4882037509455415), VectorValue(0.9507072731273287, 0.02464636343633564), - VectorValue(0.1162960196779266, 0.25545422863851736), - VectorValue(0.021382490256170623,0.12727971723358936), + VectorValue(0.1162960196779266, 0.25545422863851736), + VectorValue(0.021382490256170623,0.12727971723358936), VectorValue(0.023034156355267166, 0.29165567973834094), - VectorValue(0.6282497516835561, 0.1162960196779266), - VectorValue(0.85133779251024,0.021382490256170623), + VectorValue(0.6282497516835561, 0.1162960196779266), + VectorValue(0.85133779251024,0.021382490256170623), VectorValue(0.6853101639063919, 0.023034156355267166), - VectorValue(0.25545422863851736, 0.6282497516835561), - VectorValue(0.12727971723358936,0.85133779251024), + VectorValue(0.25545422863851736, 0.6282497516835561), + VectorValue(0.12727971723358936,0.85133779251024), VectorValue(0.29165567973834094, 0.6853101639063919), - VectorValue(0.25545422863851736, 0.1162960196779266), - VectorValue(0.12727971723358936,0.021382490256170623), + VectorValue(0.25545422863851736, 0.1162960196779266), + VectorValue(0.12727971723358936,0.021382490256170623), VectorValue(0.29165567973834094, 0.023034156355267166), - VectorValue(0.6282497516835561, 0.25545422863851736), - VectorValue(0.85133779251024,0.12727971723358936), + VectorValue(0.6282497516835561, 0.25545422863851736), + VectorValue(0.85133779251024,0.12727971723358936), VectorValue(0.6853101639063919, 0.29165567973834094), - VectorValue(0.1162960196779266, 0.6282497516835561), - VectorValue(0.021382490256170623,0.85133779251024), + VectorValue(0.1162960196779266, 0.6282497516835561), + VectorValue(0.021382490256170623,0.85133779251024), VectorValue(0.023034156355267166, 0.6853101639063919) ]; w = [ @@ -961,7 +976,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.009831548292802588, 0.5701446928909732), VectorValue(0.05498747914298685, 0.6118777035474257), VectorValue(0.01073721285601111, 0.7086813757203236), - ] + ] w = [ 0.013910110701453116, 0.009173462974252915, 0.0021612754106655778, 0.007101825303408441, 0.009452399933232448, 0.014083201307520249, @@ -1080,7 +1095,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.04945106556854055, 0.8217191264694079), VectorValue(0.010254635872924515, 0.6287919561081533), VectorValue(0.010301903643423904, 0.9339785312842042), - ] + ] w = [ 0.01072556096456617, 0.0022189148485329394, 0.011500352326641932, 0.006828016226115099, 0.009727620930375354, 0.006107205081692191, @@ -1210,7 +1225,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.048254924114641384, 0.7754476410608586), VectorValue(0.009163909248185229, 0.7468454447123217), VectorValue(0.0017984649889483744, 0.9802672139581126), - ] + ] w = [ 0.006746541941805331, 0.006930699762117096, 0.010537881978726092, 0.008010649562574445, 0.009426546276920644, 0.002644669832992209, @@ -1350,7 +1365,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.1548301554055162, 0.5288655369406456), VectorValue(0.014758969729945169, 0.5874824534670472), VectorValue(0.03299370819253279, 0.688212121993365), - ] + ] w = [ 0.012626530161518105, 0.001957870129516468, 0.00569894463390038, 0.004479958512756771, 0.011837304231564011, 0.011903931443749882, @@ -1502,7 +1517,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.09556626952736523, 0.724037578585869), VectorValue(0.007987921880847964, 0.8172747318363464), VectorValue(0.008074910870208776, 0.9546336170785), - ] + ] w = [ 0.00627284492280016, 0.006555266350942619, 0.0051895080282000966, 0.0019168498654645917, 0.0003086272527483216, 0.002171623361085349, @@ -1665,7 +1680,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.02454006024752439, 0.7188645300434557), VectorValue(0.007188828261693038, 0.9517423526265223), VectorValue(0.0008914643174981278, 0.7196923470332413), - ] + ] w = [ 0.006844925774136122, 0.005793631618005297, 0.009008820350850738, 0.001698648860952368, 0.005745762931282399, 0.00795565506872921, @@ -1840,7 +1855,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.02278459925089566, 0.7606687342756272), VectorValue(0.009473297912213558, 0.6776281990129065), VectorValue(0.0004640077321756526, 0.7731011948601068), - ] + ] w = [ 0.010243331294611621, 0.002456912651483009, 0.00026347655834093594, 0.002651079590933673, 0.00973403391859144, 0.00976782346162377, @@ -2233,7 +2248,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.018242291012294715, 0.8895285197924231), VectorValue(0.0012002556014871519, 0.92850900392038), ] - + w = [0.007181233150323068, 0.00015556760434074816, 0.0044258525054465805, 0.007105293195224093, 0.006546374294544483, 0.0039049716855431705, @@ -2471,7 +2486,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.13953560718108263, 0.5930980425461418), VectorValue(0.008066585704166612, 0.5861680189969418), VectorValue(0.00012344681228740494, 0.8135558255123531), - ] + ] w = [ 0.0007582310515784511, 0.005585550147583929, 0.0014302457530784435, 0.006269601721311614, 0.005285708929113789, 0.0030672005823594553, @@ -2700,7 +2715,7 @@ function _xiaogimbutas_quad_tri(degree) VectorValue(0.005691211445416102, 0.8588999350346495), VectorValue(0.005162347016621321, 0.5986161382437196), VectorValue(0.000533708660694491, 0.9699822487416316), - ] + ] w = [8.586495068552472e-05, 0.001900966334207759, 0.0014222168338437478, 0.0038920643661654237, 0.0004232750726644445, 0.0036934559174571445, @@ -2809,7 +2824,7 @@ function _xiaogimbutas_quad_tet(degree) """ error(msg) end - + if (degree == 1) x = [ VectorValue(0.25, 0.25, 0.25) @@ -2834,7 +2849,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.03787163178235702, 0.170816925164989, 0.1528181430909273), VectorValue(0.1248048621652472, 0.1586851632274406, 0.5856628056552158), VectorValue(0.1414827519695045, 0.5712260521491151, 0.1469183900871696), - ] + ] w = [0.020387000459557516, 0.021344402118457815, 0.022094671190740867, 0.0234374016100672, 0.0374025278195929, 0.042000663468250383]; return x, w @@ -2873,7 +2888,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.06734224221009824, 0.3108859192633006, 0.3108859192633007), VectorValue(0.3108859192633006, 0.3108859192633007, 0.3108859192633006), VectorValue(0.3108859192633006, 0.3108859192633007, 0.06734224221009814), - ] + ] w = [0.0070910034628469025, 0.007091003462846909, 0.007091003462846909, 0.007091003462846912, 0.007091003462846912, 0.0070910034628469155, 0.012248840519393652, 0.012248840519393652, 0.012248840519393655, @@ -2905,7 +2920,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.1521038113099309, 0.1246499636374863, 0.201234567364421), VectorValue(0.3041692653497818, 0.3191942803489312, 0.04438334435720821), VectorValue(0.2558207842649862, 0.2794200529459882, 0.269569929633272), - ] + ] w = [ 0.0011826324752765881, 0.001206879481977829, 0.0017372226206159916, 0.0026542465308339587, 0.0037609445463571384, 0.0040385478129073915, @@ -3009,7 +3024,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.1646955059003284, 0.2495207660799524, 0.249905550725755), VectorValue(0.4118962036484124, 0.1582590933458902, 0.2073917351406235), VectorValue(0.2003307068778031, 0.2274620499867547, 0.4489545286636978), - ] + ] w = [ 0.0002523242325191773, 0.000290863922550942, 0.00034651409450314665, 0.0004244834198904958, 0.0011495713860626099, 0.0012535250586434693, @@ -3086,7 +3101,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.2709662131081065, 0.3336191905680228, 0.1998230301014543), VectorValue(0.3787443941211687, 0.143482922419707, 0.3469408062547597), VectorValue(0.1819772234774806, 0.1650628409073057, 0.3092464405790883), - ] + ] w = [ 0.0007162016044045418, 0.0009077771144629987, 0.0009168420416400242, 0.0009750266138313078, 0.0011001900925731798, 0.0011235667369375677, @@ -3184,7 +3199,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.3395130626209017, 0.1286811682481305, 0.4110926574949583), VectorValue(0.1286811682481935, 0.1207131116358556, 0.4110926574950111), VectorValue(0.2570719807624278, 0.2570719807625507, 0.2287840577124626), - ] + ] w = [6.365271938318e-05, 0.00013168318579095435, 0.00013168318579197532, 0.00013168318579277535, 0.0002820943065241142, 0.0002820943065245265, @@ -3320,7 +3335,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.3107744236696469, 0.3006898038543989, 0.2609901451144841), VectorValue(0.3006898038549698, 0.1275456273614465, 0.260990145113821), VectorValue(0.296008469913471, 0.2960084699134847, 0.1119745902596869), - ] + ] w = [0.00018520507764804083, 0.00024392157838708066, 0.000243921578397049, 0.000243921578397797, 0.0004426544798853663, 0.00044265447988711795, @@ -3705,7 +3720,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.4002127949623002, 0.1108495022995989, 0.3263197105354845), VectorValue(0.1626179922025704, 0.4002127949623043, 0.326319710535522), VectorValue(0.2505588869395824, 0.2505588869395491, 0.2483233391813068), - ] + ] w = [4.134324458614672e-05, 0.00010841532703717533, 0.00010841532703736251, 0.00010841532703751804, 0.00021516249282564702, 0.00021516249282587435, @@ -3959,7 +3974,7 @@ function _xiaogimbutas_quad_tet(degree) VectorValue(0.332296083385749, 0.3494651707568161, 0.1644327097772045), VectorValue(0.3494651707565804, 0.1538060360803426, 0.1644327097771141), VectorValue(0.2432210319415151, 0.2432210319416702, 0.2703369041747087), - ] + ] w = [2.2105078592571967e-05, 5.104037863193469e-05, 5.1040378634203715e-05, 5.104037863543036e-05, 6.442538948289391e-05, 6.442538948628821e-05, diff --git a/src/ReferenceFEs/deprecated/BDMRefFEs.jl b/src/ReferenceFEs/deprecated/BDMRefFEs.jl new file mode 100644 index 000000000..9fe4c7798 --- /dev/null +++ b/src/ReferenceFEs/deprecated/BDMRefFEs.jl @@ -0,0 +1,190 @@ +struct BDM <: DivConforming end + +const bdm = BDM() + +""" +BDMRefFE(::Type{et},p::Polytope,order::Integer) where et + +The `order` argument has the following meaning: the divergence of the functions in this basis +is in the P space of degree `order-1`. + +""" +function BDMRefFE(::Type{et},p::Polytope,order::Integer) where et + + D = num_dims(p) + + vet = VectorValue{num_dims(p),et} + + if is_simplex(p) + prebasis = MonomialBasis(vet,p,order) + else + @notimplemented "BDM Reference FE only available for simplices" + end + + nf_nodes, nf_moments = _BDM_nodes_and_moments(et,p,order,GenericField(identity)) + + face_own_dofs = _face_own_dofs_from_moments(nf_moments) + + face_dofs = face_own_dofs + + dof_basis = MomentBasedDofBasis(nf_nodes, nf_moments) + + ndofs = num_dofs(dof_basis) + + metadata = nothing + + reffe = GenericRefFE{BDM}( + ndofs, + p, + prebasis, + dof_basis, + DivConformity(), + metadata, + face_dofs) + + reffe +end + +function ReferenceFE(p::Polytope,::BDM, order) + BDMRefFE(Float64,p,order) +end + +function ReferenceFE(p::Polytope,::BDM,::Type{T}, order) where T + BDMRefFE(T,p,order) +end + +function Conformity(reffe::GenericRefFE{BDM},sym::Symbol) + hdiv = (:Hdiv,:HDiv) + if sym == :L2 + L2Conformity() + elseif sym in hdiv + DivConformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a BDM reference FE. + + Possible values of conformity for this reference fe are $((:L2, hdiv...)). + """ + end + end + + function get_face_own_dofs(reffe::GenericRefFE{BDM}, conf::DivConformity) + get_face_dofs(reffe) + end + + function _BDM_nodes_and_moments(::Type{et}, p::Polytope, order::Integer, phi::Field) where et + + D = num_dims(p) + ft = VectorValue{D,et} + pt = Point{D,et} + + nf_nodes = [ zeros(pt,0) for face in 1:num_faces(p)] + nf_moments = [ zeros(ft,0,0) for face in 1:num_faces(p)] + + fcips, fmoments = _BDM_face_values(p,et,order,phi) + frange = get_dimrange(p,D-1) + nf_nodes[frange] = fcips + nf_moments[frange] = fmoments + + if (order > 1) + ccips, cmoments = _BDM_cell_values(p,et,order,phi) + crange = get_dimrange(p,D) + nf_nodes[crange] = ccips + nf_moments[crange] = cmoments + end + + nf_nodes, nf_moments + end + + function _BDM_face_moments(p, fshfs, c_fips, fcips, fwips,phi) + nc = length(c_fips) + cfshfs = fill(fshfs, nc) + cvals = lazy_map(evaluate,cfshfs,c_fips) + cvals = [fwips[i].*cvals[i] for i in 1:nc] + fns = get_facet_normal(p) + + # Must express the normal in terms of the real/reference system of + # coordinates (depending if phi≡I or phi is a mapping, resp.) + # Hence, J = transpose(grad(phi)) + + Jt = fill(∇(phi),nc) + Jt_inv = lazy_map(Operation(pinvJt),Jt) + det_Jt = lazy_map(Operation(meas),Jt) + change = lazy_map(*,det_Jt,Jt_inv) + change_ips = lazy_map(evaluate,change,fcips) + + cvals = [ _broadcast(typeof(n),n,J.*b) for (n,b,J) in zip(fns,cvals,change_ips)] + + return cvals + end + + # It provides for every face the nodes and the moments arrays + function _BDM_face_values(p,et,order,phi) + + # Reference facet + @check is_simplex(p) "We are assuming that all n-faces of the same n-dim are the same." + fp = Polytope{num_dims(p)-1}(p,1) + + # geomap from ref face to polytope faces + fgeomap = _ref_face_to_faces_geomap(p,fp) + + # Nodes are integration points (for exact integration) + # Thus, we define the integration points in the reference + # face polytope (fips and wips). Next, we consider the + # n-face-wise arrays of nodes in fp (constant cell array c_fips) + # the one of the points in the polytope after applying the geopmap + # (fcips), and the weights for these nodes (fwips, a constant cell array) + # Nodes (fcips) + degree = (order)*2 + fquad = Quadrature(fp,degree) + fips = get_coordinates(fquad) + wips = get_weights(fquad) + + c_fips, fcips, fwips = _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) + + # Moments (fmoments) + # The BDM prebasis is expressed in terms of shape function + fshfs = MonomialBasis(et,fp,order) + + # Face moments, i.e., M(Fi)_{ab} = q_RF^a(xgp_RFi^b) w_Fi^b n_Fi ⋅ () + fmoments = _BDM_face_moments(p, fshfs, c_fips, fcips, fwips, phi) + + return fcips, fmoments + + end + + function _BDM_cell_moments(p, cbasis, ccips, cwips) + # Interior DOFs-related basis evaluated at interior integration points + ishfs_iips = evaluate(cbasis,ccips) + return cwips.⋅ishfs_iips + end + + # It provides for every cell the nodes and the moments arrays + function _BDM_cell_values(p,et,order,phi) + # Compute integration points at interior + degree = 2*(order) + iquad = Quadrature(p,degree) + ccips = get_coordinates(iquad) + cwips = get_weights(iquad) + + # Cell moments, i.e., M(C)_{ab} = q_C^a(xgp_C^b) w_C^b ⋅ () + if is_simplex(p) + cbasis = Polynomials.NedelecPrebasisOnSimplex{num_dims(p)}(order-2) + else + @notimplemented + end + cell_moments = _BDM_cell_moments(p, cbasis, ccips, cwips ) + + # Must scale weights using phi map to get the correct integrals + # scaling = meas(grad(phi)) + Jt = ∇(phi) + Jt_inv = pinvJt(Jt) + det_Jt = meas(Jt) + change = det_Jt*Jt_inv + change_ips = evaluate(change,ccips) + + cmoments = change_ips.⋅cell_moments + + return [ccips], [cmoments] + + end diff --git a/src/ReferenceFEs/deprecated/NedelecRefFEs.jl b/src/ReferenceFEs/deprecated/NedelecRefFEs.jl new file mode 100644 index 000000000..753354db7 --- /dev/null +++ b/src/ReferenceFEs/deprecated/NedelecRefFEs.jl @@ -0,0 +1,368 @@ +struct CurlConformity <: Conformity end + +struct Nedelec <: ReferenceFEName end + +const nedelec = Nedelec() + +""" + NedelecRefFE(::Type{et},p::Polytope,order::Integer) where et + +The `order` argument has the following meaning: the curl of the functions in this basis +is in the Q space of degree `order`. +""" +function NedelecRefFE(::Type{et},p::Polytope,order::Integer) where et + + # @santiagobadia : Project, go to complex numbers + D = num_dims(p) + + if is_n_cube(p) + prebasis = QGradMonomialBasis(Val(D),et,order) + elseif is_simplex(p) + prebasis = Polynomials.NedelecPrebasisOnSimplex{D}(order) + else + @unreachable "Only implemented for n-cubes and simplices" + end + + nf_nodes, nf_moments = _Nedelec_nodes_and_moments(et,p,order) + + face_own_dofs = _face_own_dofs_from_moments(nf_moments) + + face_dofs = face_own_dofs + + dof_basis = MomentBasedDofBasis(nf_nodes, nf_moments) + + ndofs = num_dofs(dof_basis) + + metadata = nothing + + reffe = GenericRefFE{Nedelec}( + ndofs, + p, + prebasis, + dof_basis, + CurlConformity(), + metadata, + face_dofs) + + reffe +end + +function ReferenceFE(p::Polytope,::Nedelec, order) + NedelecRefFE(Float64,p,order) +end + +function ReferenceFE(p::Polytope,::Nedelec,::Type{T}, order) where T + NedelecRefFE(T,p,order) +end + +function Conformity(reffe::GenericRefFE{Nedelec},sym::Symbol) + hcurl = (:Hcurl,:HCurl) + if sym == :L2 + L2Conformity() + elseif sym in hcurl + CurlConformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a Nedelec reference FE. + + Possible values of conformity for this reference fe are $((:L2, hcurl...)). + """ + end +end + +function get_face_own_dofs(reffe::GenericRefFE{Nedelec}, conf::CurlConformity) + reffe.face_dofs # For Nedelec, this member variable holds the face owned dofs +end + +function get_face_own_dofs(reffe::GenericRefFE{Nedelec}, conf::L2Conformity) + face_own_dofs=[Int[] for i in 1:num_faces(reffe)] + face_own_dofs[end]=collect(1:num_dofs(reffe)) + face_own_dofs +end + +function get_face_dofs(reffe::GenericRefFE{Nedelec,Dc}) where Dc + face_dofs=[Int[] for i in 1:num_faces(reffe)] + face_own_dofs=get_face_own_dofs(reffe) + p = get_polytope(reffe) + for d=1:Dc # Starting from edges, vertices do not own DoFs for Nedelec + first_face = get_offset(p,d) + nfaces = num_faces(reffe,d) + for face=first_face+1:first_face+nfaces + for df=1:d-1 + face_faces = get_faces(p,d,df) + first_cface = get_offset(p,df) + for cface in face_faces[face-first_face] + cface_own_dofs = face_own_dofs[first_cface+cface] + for dof in cface_own_dofs + push!(face_dofs[face],dof) + end + end + end + for dof in face_own_dofs[face] + push!(face_dofs[face],dof) + end + end + end + face_dofs +end + + +function _Nedelec_nodes_and_moments(::Type{et}, p::Polytope, order::Integer) where et + + @notimplementedif !( is_n_cube(p) || (is_simplex(p) ) ) + + D = num_dims(p) + ft = VectorValue{D,et} + pt = Point{D,et} + + nf_nodes = [ zeros(pt,0) for face in 1:num_faces(p)] + nf_moments = [ zeros(ft,0,0) for face in 1:num_faces(p)] + + ecips, emoments = _Nedelec_edge_values(p,et,order) + erange = get_dimrange(p,1) + nf_nodes[erange] = ecips + nf_moments[erange] = emoments + + if ( num_dims(p) == 3 && order > 0) + + if is_n_cube(p) + fcips, fmoments = _Nedelec_face_values(p,et,order) + else + fcips, fmoments = _Nedelec_face_values_simplex(p,et,order) + end + + frange = get_dimrange(p,D-1) + nf_nodes[frange] = fcips + nf_moments[frange] = fmoments + + end + + if ( is_n_cube(p) && order > 0) || ( is_simplex(p) && order > D-2) + + ccips, cmoments = _Nedelec_cell_values(p,et,order) + crange = get_dimrange(p,D) + nf_nodes[crange] = ccips + nf_moments[crange] = cmoments + + end + + nf_nodes, nf_moments +end + +function _Nedelec_edge_values(p,et,order) + + # Reference facet + dim1 = 1 + ep = Polytope{dim1}(p,1) + + # geomap from ref face to polytope faces + egeomap = _ref_face_to_faces_geomap(p,ep) + + # Compute integration points at all polynomial edges + degree = (order)*2 + equad = Quadrature(ep,degree) + cips = get_coordinates(equad) + wips = get_weights(equad) + + + c_eips, ecips, ewips = _nfaces_evaluation_points_weights(p, egeomap, cips, wips) + + # Edge moments, i.e., M(Ei)_{ab} = q_RE^a(xgp_REi^b) w_Fi^b t_Ei ⋅ () + eshfs = monomial_basis(et,ep,order) + emoments = _Nedelec_edge_moments(p, eshfs, c_eips, ecips, ewips) + + return ecips, emoments + +end + +function _Nedelec_edge_moments(p, fshfs, c_fips, fcips, fwips) + ts = get_edge_tangent(p) + nc = length(c_fips) + cfshfs = fill(fshfs, nc) + cvals = lazy_map(evaluate,cfshfs,c_fips) + cvals = [fwips[i].*cvals[i] for i in 1:nc] + # @santiagobadia : Only working for oriented meshes now + cvals = [ _broadcast(typeof(t),t,b) for (t,b) in zip(ts,cvals)] + return cvals +end + +function _Nedelec_face_values(p,et,order) + + # Reference facet + @assert is_n_cube(p) "We are assuming that all n-faces of the same n-dim are the same." + fp = Polytope{num_dims(p)-1}(p,1) + + # geomap from ref face to polytope faces + fgeomap = _ref_face_to_faces_geomap(p,fp) + + # Compute integration points at all polynomial edges + degree = (order)*2 + fquad = Quadrature(fp,degree) + fips = get_coordinates(fquad) + wips = get_weights(fquad) + + c_fips, fcips, fwips = _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) + + # Face moments, i.e., M(Fi)_{ab} = w_Fi^b q_RF^a(xgp_RFi^b) (n_Fi × ()) + fshfs = QGradMonomialBasis(Val(num_dims(fp)),et,order-1) + + fmoments = _Nedelec_face_moments(p, fshfs, c_fips, fcips, fwips) + + return fcips, fmoments + +end + +function _Nedelec_face_moments(p, fshfs, c_fips, fcips, fwips) + nc = length(c_fips) + cfshfs = fill(fshfs, nc) + cvals = lazy_map(evaluate,cfshfs,c_fips) + + fvs = _nfaces_vertices(Float64,p,num_dims(p)-1) + fts = [hcat([vs[2]-vs[1]...],[vs[3]-vs[1]...]) for vs in fvs] + + # Ref facet FE functions evaluated at the facet integration points (in ref facet) + cvals = [fwips[i].*cvals[i] for i in 1:nc] + + fns = get_facet_normal(p) + os = get_facet_orientations(p) + # @santiagobadia : Temporary hack for making it work for structured hex meshes + ft = eltype(fns) + cvals = [ _broadcast_extend(ft,Tm,b) for (Tm,b) in zip(fts,cvals)] + cvals = [ _broadcast_cross(ft,n*o,b) for (n,o,b) in zip(fns,os,cvals)] + return cvals +end + +function _Nedelec_face_values_simplex(p,et,order) + + # Reference facet + @assert is_simplex(p) "We are assuming that all n-faces of the same n-dim are the same." + fp = Polytope{num_dims(p)-1}(p,1) + + # geomap from ref face to polytope faces + fgeomap = _ref_face_to_faces_geomap(p,fp) + + # Compute integration points at all polynomial edges + degree = (order)*2 + fquad = Quadrature(fp,degree) + fips = get_coordinates(fquad) + wips = get_weights(fquad) + + c_fips, fcips, fwips, fJtips = _nfaces_evaluation_points_weights_with_jac(p, fgeomap, fips, wips) + + Df = num_dims(fp) + fshfs = MonomialBasis(Val(Df),VectorValue{Df,et},order-1,(e,k)->sum(e)<=k) + + fmoments = _Nedelec_face_moments_simplex(p, fshfs, c_fips, fcips, fwips, fJtips) + + return fcips, fmoments + +end + +function _nfaces_evaluation_points_weights_with_jac(p, fgeomap, fips, wips) + nc = length(fgeomap) + c_fips = fill(fips,nc) + c_wips = fill(wips,nc) + pquad = lazy_map(evaluate,fgeomap,c_fips) + ## Must account for diagonals in simplex discretizations to get the correct + ## scaling + Jt1 = lazy_map(∇,fgeomap) + Jt1_ips = lazy_map(evaluate,Jt1,c_fips) + #det_J = lazy_map(Broadcasting(meas),Jt1_ips) + #c_detwips = collect(lazy_map(Broadcasting(*),c_wips,det_J)) + c_detwips = c_wips + c_fips, pquad, c_detwips, Jt1_ips +end + +function _Nedelec_face_moments_simplex(p, fshfs, c_fips, fcips, fwips, fJtips) + nc = length(c_fips) + cfshfs = fill(fshfs, nc) + cfshfs_fips = lazy_map(evaluate,cfshfs,c_fips) + function weigth(qij,Jti,wi) + Ji = transpose(Jti) + Ji⋅qij*wi + end + cvals = map(Broadcasting(weigth),cfshfs_fips,fJtips,fwips) + return cvals +end + +# It provides for every cell the nodes and the moments arrays +function _Nedelec_cell_values(p,et,order) + + # Compute integration points at interior + degree = 2*(order) + iquad = Quadrature(p,degree) + ccips = get_coordinates(iquad) + cwips = get_weights(iquad) + + # Cell moments, i.e., M(C)_{ab} = q_C^a(xgp_C^b) w_C^b ⋅ () + if is_n_cube(p) + cbasis = QCurlGradMonomialBasis(Val(num_dims(p)),et,order-1) + else + D = num_dims(p) + cbasis = MonomialBasis(Val(D),VectorValue{D,et},order-D+1,(e,k)->sum(e)<=k) + end + cmoments = _Nedelec_cell_moments(p, cbasis, ccips, cwips ) + + return [ccips], [cmoments] + +end + +const _Nedelec_cell_moments = _RT_cell_moments + +function _broadcast_cross(::Type{T},n,b) where T + c = Array{T}(undef,size(b)) + for (ii, i) in enumerate(b) + c[ii] = T(cross(get_array(i),get_array(n)))# cross product + end + return c +end + +# Moves bi values from 2D to 3D, by multiplying by a 3x2 matrix Tm +# Tm = [1.0 0.0; 0.0 1.0; 0.0 0.0] for instance +function _broadcast_extend(::Type{T},Tm,b) where T + c = Array{T}(undef,size(b)) + for (ii,i) in enumerate(b) + c[ii] = T(Tm*[i...]) + end + return c +end + +struct CoVariantPiolaMap <: Map end + +function evaluate!( + cache, + ::Broadcasting{typeof(∇)}, + a::Fields.BroadcastOpFieldArray{CoVariantPiolaMap}) + v, Jt = a.args + # Assuming J comes from an affine map + ∇v = Broadcasting(∇)(v) + k = CoVariantPiolaMap() + Broadcasting(Operation(k))(∇v,Jt) +end + +function lazy_map( + ::Broadcasting{typeof(gradient)}, + a::LazyArray{<:Fill{Broadcasting{Operation{CoVariantPiolaMap}}}}) + v, Jt = a.args + ∇v = lazy_map(Broadcasting(∇),v) + k = CoVariantPiolaMap() + lazy_map(Broadcasting(Operation(k)),∇v,Jt) +end + +function evaluate!(cache,::CoVariantPiolaMap,v::Number,Jt::Number) + v⋅transpose(inv(Jt)) # we multiply by the right side to compute the gradient correctly +end + +function evaluate!(cache,k::CoVariantPiolaMap,v::AbstractVector{<:Field},phi::Field) + Jt = ∇(phi) + Broadcasting(Operation(k))(v,Jt) +end + +function lazy_map( + k::CoVariantPiolaMap, + cell_ref_shapefuns::AbstractArray{<:AbstractArray{<:Field}}, + cell_map::AbstractArray{<:Field}) + + cell_Jt = lazy_map(∇,cell_map) + lazy_map(Broadcasting(Operation(k)),cell_ref_shapefuns,cell_Jt) +end diff --git a/src/ReferenceFEs/deprecated/RaviartThomasRefFEs.jl b/src/ReferenceFEs/deprecated/RaviartThomasRefFEs.jl new file mode 100644 index 000000000..006d92e65 --- /dev/null +++ b/src/ReferenceFEs/deprecated/RaviartThomasRefFEs.jl @@ -0,0 +1,468 @@ +struct DivConformity <: Conformity end + +abstract type DivConforming <: ReferenceFEName end + +struct RaviartThomas <: DivConforming end + +const raviart_thomas = RaviartThomas() + +""" + RaviartThomasRefFE(::Type{et},p::Polytope,order::Integer) where et + +The `order` argument has the following meaning: the divergence of the functions in this basis +is in the Q space of degree `order`. + +""" +function RaviartThomasRefFE( + ::Type{et},p::Polytope,order::Integer;basis_type=:monomial,phi=GenericField(identity) +) where et + @assert basis_type ∈ (:monomial, :legendre, :chebyshev) + + D = num_dims(p) + + if is_n_cube(p) && basis_type == :monomial + prebasis = QCurlGradMonomialBasis(Val(D),et,order) + elseif is_simplex(p) && basis_type == :monomial + prebasis = PCurlGradMonomialBasis(Val(D),et,order) + elseif is_n_cube(p) && basis_type == :legendre + prebasis = QCurlGradLegendreBasis(Val(D),et,order) + elseif is_simplex(p) && basis_type == :legendre + prebasis = PCurlGradLegendreBasis(Val(D),et,order) + elseif is_n_cube(p) && basis_type == :chebyshev + prebasis = QCurlGradChebyshevBasis(Val(D),et,order) + else + @notimplemented "H(div) Reference FE only available for cubes and simplices" + end + + nf_nodes, nf_moments = _RT_nodes_and_moments(et,p,order,phi) + + face_own_dofs = _face_own_dofs_from_moments(nf_moments) + + face_dofs = face_own_dofs + + dof_basis = MomentBasedDofBasis(nf_nodes, nf_moments) + + ndofs = num_dofs(dof_basis) + + metadata = nothing + + reffe = GenericRefFE{RaviartThomas}( + ndofs, + p, + prebasis, + dof_basis, + DivConformity(), + metadata, + face_dofs) + + reffe +end + +function ReferenceFE(p::Polytope,::RaviartThomas,order;kwargs...) + RaviartThomasRefFE(Float64,p,order;kwargs...) +end + +function ReferenceFE(p::Polytope,::RaviartThomas,::Type{T},order;kwargs...) where T + RaviartThomasRefFE(T,p,order;kwargs...) +end + +function Conformity(reffe::GenericRefFE{RaviartThomas},sym::Symbol) + hdiv = (:Hdiv,:HDiv) + if sym == :L2 + L2Conformity() + elseif sym in hdiv + DivConformity() + else + @unreachable """\n + It is not possible to use conformity = $sym on a Raviart Thomas reference FE. + + Possible values of conformity for this reference fe are $((:L2, hdiv...)). + """ + end +end + +function get_face_own_dofs(reffe::GenericRefFE{RaviartThomas}, conf::DivConformity) + get_face_dofs(reffe) +end + +function _RT_nodes_and_moments(::Type{et}, p::Polytope, order::Integer, phi::Field) where et + + D = num_dims(p) + ft = VectorValue{D,et} + pt = Point{D,et} + + nf_nodes = [ zeros(pt,0) for face in 1:num_faces(p)] + nf_moments = [ zeros(ft,0,0) for face in 1:num_faces(p)] + + fcips, fmoments = _RT_face_values(p,et,order,phi) + frange = get_dimrange(p,D-1) + nf_nodes[frange] = fcips + nf_moments[frange] = fmoments + + if (order > 0) + ccips, cmoments = _RT_cell_values(p,et,order,phi) + crange = get_dimrange(p,D) + nf_nodes[crange] = ccips + nf_moments[crange] = cmoments + end + + nf_nodes, nf_moments +end + +# Ref FE to faces geomaps +function _ref_face_to_faces_geomap(p,fp) + cfvs = get_face_coordinates(p,num_dims(fp)) + nc = length(cfvs) + freffe = LagrangianRefFE(Float64,fp,1) + fshfs = get_shapefuns(freffe) + cfshfs = fill(fshfs, nc) + fgeomap = lazy_map(linear_combination,cfvs,cfshfs) +end + +function _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) + nc = length(fgeomap) + c_fips = fill(fips,nc) + c_wips = fill(wips,nc) + pquad = lazy_map(evaluate,fgeomap,c_fips) + + if is_n_cube(p) + c_detwips = c_wips + elseif is_simplex(p) + # Must account for diagonals in simplex discretizations to get the correct + # scaling + Jt1 = lazy_map(∇,fgeomap) + Jt1_ips = lazy_map(evaluate,Jt1,c_fips) + det_J = lazy_map(Broadcasting(meas),Jt1_ips) + + c_detwips = collect(lazy_map(Broadcasting(*),c_wips,det_J)) + end + + c_fips, pquad, c_detwips +end + +function _broadcast(::Type{T},n,b) where T + c = Array{T}(undef,size(b)) + for (ii, i) in enumerate(b) + c[ii] = i⋅n + end + return c +end + +function _RT_face_moments(p, fshfs, c_fips, fcips, fwips, phi) + nc = length(c_fips) + cfshfs = fill(fshfs, nc) + cvals = lazy_map(evaluate,cfshfs,c_fips) + cvals = [fwips[i].*cvals[i] for i in 1:nc] + fns = get_facet_normal(p) + + # Must express the normal in terms of the real/reference system of + # coordinates (depending if phi≡I or phi is a mapping, resp.) + # Hence, J = transpose(grad(phi)) + + Jt = fill(∇(phi),nc) + Jt_inv = lazy_map(Operation(pinvJt),Jt) + det_Jt = lazy_map(Operation(meas),Jt) + change = lazy_map(*,det_Jt,Jt_inv) + change_ips = lazy_map(evaluate,change,fcips) + + cvals = [ _broadcast(typeof(n),n,J.*b) for (n,b,J) in zip(fns,cvals,change_ips)] + return cvals +end + +# It provides for every face the nodes and the moments arrays +function _RT_face_values(p,et,order,phi) + + # Reference facet + @assert is_simplex(p) || is_n_cube(p) "We are assuming that all n-faces of the same n-dim are the same." + fp = Polytope{num_dims(p)-1}(p,1) + + # geomap from ref face to polytope faces + fgeomap = _ref_face_to_faces_geomap(p,fp) + + # Nodes are integration points (for exact integration) + # Thus, we define the integration points in the reference + # face polytope (fips and wips). Next, we consider the + # n-face-wise arrays of nodes in fp (constant cell array c_fips) + # the one of the points in the polytope after applying the geopmap + # (fcips), and the weights for these nodes (fwips, a constant cell array) + # Nodes (fcips) + degree = (order)*2 + fquad = Quadrature(fp,degree) + fips = get_coordinates(fquad) + wips = get_weights(fquad) + + c_fips, fcips, fwips = _nfaces_evaluation_points_weights(p, fgeomap, fips, wips) + + # Moments (fmoments) + # The RT prebasis is expressed in terms of shape function + #fshfs = MonomialBasis(et,fp,order) + fshfs = legendreBasis(et,fp,order) + #fshfs = chebyshevBasis(et,fp,order) + #fshfs = get_shapefuns(LagrangianRefFE(et,fp,order)) + + # Face moments, i.e., M(Fi)_{ab} = q_RF^a(xgp_RFi^b) w_Fi^b n_Fi ⋅ () + fmoments = _RT_face_moments(p, fshfs, c_fips, fcips, fwips, phi) + + return fcips, fmoments +end + +function legendreBasis(::Type{T},p::Polytope,orders) where T + compute_legendre_basis(T,p,orders) +end +function legendreBasis(::Type{T},p::Polytope{D},order::Int) where {D,T} + orders = tfill(order,Val{D}()) + legendreBasis(T,p,orders) +end +function compute_legendre_basis(::Type{T},p::ExtrusionPolytope{D},orders) where {D,T} + extrusion = Tuple(p.extrusion) + terms = _monomial_terms(extrusion,orders) + LegendreBasis(Val(D),T,orders,terms) +end + +function chebyshevBasis(::Type{T},p::Polytope,orders) where T + compute_chebyshev_basis(T,p,orders) +end +function chebyshevBasis(::Type{T},p::Polytope{D},order::Int) where {D,T} + orders = tfill(order,Val{D}()) + chebyshevBasis(T,p,orders) +end +function compute_chebyshev_basis(::Type{T},p::ExtrusionPolytope{D},orders) where {D,T} + extrusion = Tuple(p.extrusion) + terms = _monomial_terms(extrusion,orders) + ChebyshevBasis(Val(D),T,orders,terms) +end + +function _RT_cell_moments(p, cbasis, ccips, cwips) + # Interior DOFs-related basis evaluated at interior integration points + ishfs_iips = evaluate(cbasis,ccips) + return cwips.⋅ishfs_iips +end + +_p_filter(e,order) = (sum(e) <= order) + +# It provides for every cell the nodes and the moments arrays +function _RT_cell_values(p,et,order,phi) + # Compute integration points at interior + degree = 2*(order) + iquad = Quadrature(p,degree) + ccips = get_coordinates(iquad) + cwips = get_weights(iquad) + + # Cell moments, i.e., M(C)_{ab} = q_C^a(xgp_C^b) w_C^b ⋅ () + if is_n_cube(p) + #cbasis = QGradMonomialBasis(Val(num_dims(p)),et,order-1) + cbasis = QGradLegendreBasis(Val(num_dims(p)),et,order-1) + #cbasis = QGradChebyshevBasis(Val(num_dims(p)),et,order-1) + #cbasis = get_shapefuns(RaviartThomasRefFE(et,p,order-1)) + elseif is_simplex(p) + T = VectorValue{num_dims(p),et} + cbasis = MonomialBasis(Val(num_dims(p)),T,order-1, _p_filter) + else + @notimplemented + end + cell_moments = _RT_cell_moments(p, cbasis, ccips, cwips ) + + # Must scale weights using phi map to get the correct integrals + # scaling = meas(grad(phi)) + Jt = ∇(phi) + Jt_inv = pinvJt(Jt) + det_Jt = meas(Jt) + change = det_Jt*Jt_inv + change_ips = evaluate(change,ccips) + + cmoments = change_ips.⋅cell_moments + + return [ccips], [cmoments] + +end + +function _face_own_dofs_from_moments(f_moments) + face_dofs = Vector{Int}[] + o = 1 + for moments in f_moments + ndofs = size(moments,2) + dofs = collect(o:(o+ndofs-1)) + push!(face_dofs,dofs) + o += ndofs + end + face_dofs +end + +struct Moment <: Dof end + +struct MomentBasedDofBasis{P,V} <: AbstractVector{Moment} + nodes::Vector{P} + face_moments::Vector{Array{V}} + face_nodes::Vector{UnitRange{Int}} + + function MomentBasedDofBasis(nodes,f_moments,f_nodes) + P = eltype(nodes) + V = eltype(eltype(f_moments)) + new{P,V}(nodes,f_moments,f_nodes) + end + + function MomentBasedDofBasis(f_nodes,f_moments) + P = eltype(eltype(f_nodes)) + V = eltype(eltype(f_moments)) + nodes = P[] + face_nodes = UnitRange{Int}[] + nfaces = length(f_nodes) + n = 1 + for fi in 1:nfaces + nodes_fi = f_nodes[fi] + nini = n + for node_fi in nodes_fi + push!(nodes,node_fi) + n += 1 + end + nend = n-1 + push!(face_nodes,nini:nend) + end + new{P,V}(nodes,f_moments,face_nodes) + end +end + +Base.size(a::MomentBasedDofBasis) = (length(a.nodes),) +Base.axes(a::MomentBasedDofBasis) = (axes(a.nodes,1),) +# @santiagobadia : Not sure we want to create the moment dofs +Base.getindex(a::MomentBasedDofBasis,i::Integer) = Moment() +Base.IndexStyle(::MomentBasedDofBasis) = IndexLinear() + +get_nodes(b::MomentBasedDofBasis) = b.nodes +get_face_moments(b::MomentBasedDofBasis) = b.face_moments +get_face_nodes_dofs(b::MomentBasedDofBasis) = b.face_nodes + +function num_dofs(b::MomentBasedDofBasis) + n = 0 + for m in b.face_moments + n += size(m,2) + end + n +end + +function return_cache(b::MomentBasedDofBasis,field) + cf = return_cache(field,b.nodes) + vals = evaluate!(cf,field,b.nodes) + ndofs = num_dofs(b) + r = _moment_dof_basis_cache(vals,ndofs) + c = CachedArray(r) + (c, cf) +end + +function _moment_dof_basis_cache(vals::AbstractVector,ndofs) + T = eltype(vals) + r = fill(zero(eltype(T))*0.,ndofs) +end + +function _moment_dof_basis_cache(vals::AbstractMatrix,ndofs) + _, npdofs = size(vals) + T = eltype(vals) + r = fill(zero(eltype(T))*0.,ndofs,npdofs) +end + +function evaluate!(cache,b::MomentBasedDofBasis,field) + c, cf = cache + vals = evaluate!(cf,field,b.nodes) + dofs = c.array + _eval_moment_dof_basis!(dofs,vals,b) + dofs +end + +function _eval_moment_dof_basis!(dofs,vals::AbstractVector,b) + o = 1 + z = zero(eltype(dofs)) + face_nodes = b.face_nodes + face_moments = b.face_moments + for face in 1:length(face_moments) + moments = face_moments[face] + if length(moments) != 0 + nodes = face_nodes[face] + ni,nj = size(moments) + for j in 1:nj + dofs[o] = z + for i in 1:ni + dofs[o] += moments[i,j]⋅vals[nodes[i]] + end + o += 1 + end + end + end +end + +function _eval_moment_dof_basis!(dofs,vals::AbstractMatrix,b) + o = 1 + na = size(vals,2) + z = zero(eltype(dofs)) + face_nodes = b.face_nodes + face_moments = b.face_moments + for face in 1:length(face_moments) + moments = face_moments[face] + if length(moments) != 0 + nodes = face_nodes[face] + ni,nj = size(moments) + for j in 1:nj + for a in 1:na + dofs[o,a] = z + for i in 1:ni + dofs[o,a] += moments[i,j]⋅vals[nodes[i],a] + end + end + o += 1 + end + end + end +end + +struct ContraVariantPiolaMap <: Map end + +function evaluate!( + cache, + ::Broadcasting{typeof(∇)}, + a::Fields.BroadcastOpFieldArray{ContraVariantPiolaMap} +) + v, Jt, sign_flip = a.args + ∇v = Broadcasting(∇)(v) + k = ContraVariantPiolaMap() + Broadcasting(Operation(k))(∇v,Jt,sign_flip) +end + +function lazy_map( + ::Broadcasting{typeof(gradient)}, + a::LazyArray{<:Fill{Broadcasting{Operation{ContraVariantPiolaMap}}}} +) + v, Jt, sign_flip = a.args + ∇v = lazy_map(Broadcasting(∇),v) + k = ContraVariantPiolaMap() + lazy_map(Broadcasting(Operation(k)),∇v,Jt,sign_flip) +end + +function lazy_map( + k::ContraVariantPiolaMap, + cell_ref_shapefuns::AbstractArray{<:AbstractArray{<:Field}}, + cell_map::AbstractArray{<:Field}, + sign_flip::AbstractArray{<:AbstractArray{<:Field}} +) + cell_Jt = lazy_map(∇,cell_map) + lazy_map(Broadcasting(Operation(k)),cell_ref_shapefuns,cell_Jt,sign_flip) +end + +function evaluate!( + cache,::ContraVariantPiolaMap, + v::Number, + Jt::Number, + sign_flip::Bool +) + idetJ = 1/meas(Jt) + ((-1)^sign_flip*v)⋅(idetJ*Jt) +end + +function evaluate!( + cache, + k::ContraVariantPiolaMap, + v::AbstractVector{<:Field}, + phi::Field, + sign_flip::AbstractVector{<:Field} +) + Jt = ∇(phi) + Broadcasting(Operation(k))(v,Jt,sign_flip) +end diff --git a/src/TensorValues/Indexing.jl b/src/TensorValues/Indexing.jl index c26854653..8b1c3ee9b 100644 --- a/src/TensorValues/Indexing.jl +++ b/src/TensorValues/Indexing.jl @@ -135,6 +135,14 @@ end @inbounds arg.data[index] end +@propagate_inbounds function getindex(arg::SkewSymTensorValue{D,T},i::Integer,j::Integer) where {D,T} + @boundscheck @check checkbounds(arg,i,j) === nothing + i == j && return zero(T) + index = _2d_skew_sym_tensor_linear_index(D,i,j) + v = @inbounds arg.data[index] + i N ? 1 : @inbounds size(a)[d] # @inbounds + return d > N ? 1 : @inbounds size(MultiValue{S})[d] # @inbounds +end +size(a::MultiValue, d::Integer) = size(typeof(a), d) + +""" + num_components(::Type{<:Number}) + num_components(a::Number) + +Total number of components of a `Number` or `MultiValue`, that is 1 for scalars +and the product of the size dimensions for a `MultiValue`. This is the same as `length`. +""" +num_components(a::Number) = num_components(typeof(a)) +num_components(::Type{<:Number}) = 1 +num_components(T::Type{<:MultiValue}) = @unreachable "$T type is too abstract to count its components, the shape (firt parametric argument) is neede." +num_components(::Type{<:MultiValue{S}}) where S = length(MultiValue{S}) + +""" + num_indep_components(::Type{<:Number}) + num_indep_components(a::Number) + +Number of independent components of a `Number`, that is `num_components` +minus the number of components determined from others by symmetries or constraints. + +For example, a `TensorValue{3,3}` has 9 independent components, a `SymTensorValue{3}` +has 6 and a `SymTracelessTensorValue{3}` has 5. But they all have 9 (non independent) components. +""" +num_indep_components(::Type{T}) where T<:Number = num_components(T) +num_indep_components(::T) where T<:Number = num_indep_components(T) + +""" +!!! warning + Deprecated in favor on [`num_components`](@ref). +""" +function n_components end +@deprecate n_components num_components + + +####################################################################### +# Other constructors and conversions implemented for more generic types +####################################################################### + +zero(::Type{V}) where V<:MultiValue{S,T} where {S,T} = V(tfill(zero(T),Val(num_indep_components(V)))) +zero(a::MultiValue) = zero(typeof(a)) + +one(::Type{V}) where V<:MultiValue = @unreachable "`one` not defined for $V" +one(a::MultiValue) = one(typeof(a)) + +function rand(rng::AbstractRNG,::Random.SamplerType{V}) where V<:MultiValue{D,T} where {D,T} + Li = num_indep_components(V) + vrand = rand(rng, SVector{Li,T}) + V(Tuple(vrand)) end + ## ATM it is not possible to implement array like axes because lazy_mapping ## operations / broadcast rely on axes(::MultiValue) adopting the Number convention to return (). #axes(::Type{<:MultiValue{S}}) where S = map(SOneTo, tuple(S.parameters...)) @@ -52,8 +111,11 @@ For multivalues, returns `M` or `typeof(m)` but with the component type (`MultiV For scalars (or any non MultiValue number), `change_eltype` returns T2. """ -change_eltype(::Type{<:Number},::Type{T}) where {T} = T -change_eltype(::Number,::Type{T2}) where {T2} = change_eltype(Number,T2) +change_eltype(::Type{<:Number}, T::Type) = T +change_eltype(::T,::Type{T2}) where {T<:Number,T2} = change_eltype(T,T2) +change_eltype(a::MultiValue,::Type{T2}) where T2 = change_eltype(typeof(a),T2) + +get_array(a::MultiValue{S,T}) where {S,T} = convert(SArray{S,T},a) """ MultiValue(a::SArray) @@ -98,8 +160,11 @@ to the `MultiValue` type T or array size and type of `a`. See also [`mutable`](@ref). """ -Mutable(::Type{MultiValue}) = @abstractmethod -Mutable(::MultiValue) = Mutable(MultiValue) +Mutable(::Type{<:MultiValue}) = @abstractmethod +# typeof(zero(...)) is for the N and L type parameters to be computed, and get +# e.g. MVector{D,T} == MMarray{Tuple{D},T,1,D} instead of MMarray{Tuple{D},T} +Mutable(::Type{<:MultiValue{S,T}}) where {S,T} = typeof(zero(MArray{S,T})) +Mutable(a::MultiValue) = Mutable(typeof(a)) """ mutable(a::MultiValue) @@ -108,45 +173,37 @@ Converts `a` into a mutable array of type `MArray` defined by `StaticArrays.jl`. See also [`Mutable`](@ref). """ -mutable(a::MultiValue) = @abstractmethod - -""" - num_components(::Type{<:Number}) - num_components(a::Number) +mutable(a::MultiValue{S}) where S = MArray{S}(Tuple(get_array(a))) -Total number of components of a `Number` or `MultiValue`, that is 1 for scalars -and the product of the size dimensions for a `MultiValue`. This is the same as `length`. -""" -num_components(::Type{<:Number}) = 1 -num_components(::Number) = num_components(Number) -num_components(T::Type{<:MultiValue}) = @unreachable "$T type is too abstract to count its components, provide a (parametric) concrete type" - -""" - num_indep_components(::Type{<:Number}) - num_indep_components(a::Number) +############################################################### +# Conversions +############################################################### -Number of independant components of a `Number`, that is `num_components` -minus the number of components determined from others by symmetries or constraints. +# Direct conversion +convert(::Type{V}, arg::AbstractArray) where V<:MultiValue{S,T} where {S,T} = V(arg) +convert(::Type{V}, arg::Tuple) where V<:MultiValue{S,T} where {S,T} = V(arg) -For example, a `TensorValue{3,3}` has 9 independant components, a `SymTensorValue{3}` -has 6 and a `SymTracelessTensorValue{3}` has 5. But they all have 9 (non independant) components. -""" -num_indep_components(::Type{T}) where T<:Number = num_components(T) -num_indep_components(::T) where T<:Number = num_indep_components(T) +# Inverse conversion +convert(::Type{<:NTuple{L,T}}, arg::MultiValue) where {L,T} = NTuple{L,T}(Tuple(arg)) -function n_components(a) - msg = "Function n_components has been removed, use num_components instead" - error(msg) -end +############################################################### +# Indexing independant components +############################################################### # This should probably not be exported, as (accessing) the data field of # MultiValue is not a public api """ -Transforms Cartesian indices to linear indices that index `MultiValue`'s private internal storage, this should'nt be used. +Previously used to transform Cartesian indices to linear indices that index `MultiValue`'s private internal storage. + +!!! warning + Deprecated, not all components of all tensors are stored anymore, so this + index is ill defined. Use `getindex` or [`indep_comp_getindex`](@ref) + instead of this. """ function data_index(::Type{<:MultiValue},i...) @abstractmethod end +@deprecate data_index getindex # The order of export of components is that of their position in the .data # field, but the actual method "choosing" the export order is @@ -155,22 +212,22 @@ end indep_comp_getindex(a::Number,i) Get the `i`th independent component of `a`. It only differs from `getindex(a,i)` -when the components of `a` are interdependant, see [`num_indep_components`](@ref). +when the components of `a` are interdependent, see [`num_indep_components`](@ref). `i` should be in `1:num_indep_components(a)`. """ -function indep_comp_getindex(a::Number,i) - @check 1 <= i <= num_indep_components(Number) - a[i] +@propagate_inbounds function indep_comp_getindex(a::Number,i) + @boundscheck @check 1 <= i <= num_indep_components(Number) + @inbounds a[i] end -function indep_comp_getindex(a::T,i) where {T<:MultiValue} - @check 1 <= i <= num_indep_components(T) - _get_data(a,i) +@propagate_inbounds function indep_comp_getindex(a::T,i) where {T<:MultiValue} + @boundscheck @check 1 <= i <= num_indep_components(T) + @inbounds _get_data(a,i) end # abstraction of Multivalue data access in case subtypes of MultiValue don't # store its data in a data field -function _get_data(a::MultiValue,i) +@propagate_inbounds function _get_data(a::MultiValue,i) a.data[i] end @@ -188,6 +245,64 @@ function indep_components_names(::Type{MultiValue{S,T,N,L}}) where {S,T,N,L} return ["$i" for i in 1:L] end +""" + component_basis(V::Type{<:MultiValue}) -> V[ Vᵢ... ] + component_basis(T::Type{<:Real}) -> [ one(T) ] + component_basis(a::T<:Number) + +Given a `Number` type `V` with N independent components, return a vector of +N values ``\\{ Vᵢ=V(eᵢ) \\}_i`` forming the component basis of ``\\{ u : u\\text{ isa }V\\}`` +(where ``\\{eᵢ\\}_i`` is the Cartesian basis of (`eltype(V)`)ᴺ). + +The `Vᵢ` verify the property that for any `u::V`, + + u = sum( indep_comp_getindex(u,i)*Vᵢ for i ∈ 1:N ) +""" +component_basis(a::Number) = component_basis(typeof(a)) +component_basis(T::Type{<:Number}) = [ one(T) ] +function component_basis(V::Type{<:MultiValue}) + T = eltype(V) + Li = num_indep_components(V) + return V[ ntuple(i -> T(i == j), Li) for j in 1:Li ] +end + +""" + representatives_of_componentbasis_dual(V::Type{<:MultiValue}) -> V[ Vᵢ... ] + representatives_of_componentbasis_dual(T::Type{<:Real}) -> [ one(T) ] + representatives_of_componentbasis_dual(a::V<:Number) + +Given a `Number` type `V` with N independent components, return a vector of +N values ``\\{ Vᵢ \\}_i`` that define the form basis ``\\{ Lⁱ := (u -> u ⊙ Vᵢ) \\}_i`` that +is the dual of the component basis ``\\{ V(eᵢ) \\}_i`` (where ``\\{eᵢ\\}_i`` is the +Cartesian basis of (`eltype(V)`)ᴺ). + +The `Lⁱ`/`Vᵢ` verify the property that for any `u::V`, + + u = V( [ Lⁱ(u) for i ∈ 1:N ]... ) + = V( [ u⊙Vᵢ for i ∈ 1:N ]... ) + +Rq, when `V` has dependent components, the `Vᵢ` are NOT a component basis because +`Vᵢ ≠ V(eᵢ)` and + + u ≠ sum( indep_comp_getindex(u,i)*Vᵢ for i ∈ 1:N ) +""" +representatives_of_componentbasis_dual(a::Number) = representatives_of_componentbasis_dual(typeof(a)) +representatives_of_componentbasis_dual(T::Type{<:Real}) = [ one(T) ] +function representatives_of_componentbasis_dual(V::Type{<:MultiValue}) + V = typeof(zero(V)) # makes V concrete for type inference + N = num_indep_components(V) + T = eltype(V) + B = component_basis(V) + + M = MMatrix{N,N,T}(undef) + for ci in CartesianIndices(M) + M[ci] = B[ci[1]] ⊙ B[ci[2]] + end + Minv = inv(M) + + return V[ Tuple(Minv[i,:]) for i in 1:N ] +end + @inline function ForwardDiff.value(x::MultiValue{S,<:ForwardDiff.Dual}) where S VT = change_eltype(x,ForwardDiff.valtype(eltype(x))) data = map(ForwardDiff.value,x.data) diff --git a/src/TensorValues/Operations.jl b/src/TensorValues/Operations.jl index 77a6bc1a8..51c6e0175 100644 --- a/src/TensorValues/Operations.jl +++ b/src/TensorValues/Operations.jl @@ -2,8 +2,9 @@ # Comparison ############################################################### -(==)(a::MultiValue,b::MultiValue) = false -(==)(a::MultiValue{S},b::MultiValue{S}) where {S} = a.data == b.data +(==)(a::Number, b::MultiValue) = false +(==)(a::MultiValue, b::MultiValue) = false +(==)(a::MultiValue{S}, b::MultiValue{S}) where S = a.data == b.data (≈)(a::MultiValue,b::MultiValue;kwargs...) = ≈(get_array(a),get_array(b);kwargs...) function (≈)( @@ -15,7 +16,7 @@ function (≈)( true end -function isless(a::MultiValue{Tuple{L}},b::MultiValue{Tuple{L}}) where L +function isless(a::MultiValue{Tuple{L}}, b::MultiValue{Tuple{L}}) where {L} for d in L:-1:1 if isless(a[d], b[d]) return true @@ -28,87 +29,83 @@ function isless(a::MultiValue{Tuple{L}},b::MultiValue{Tuple{L}}) where L false end -isless(a::Number,b::MultiValue) = all(isless.(a, b.data)) -isless(a::MultiValue,b::MultiValue) = @unreachable "Comparison is not defined between tensor of order greater than 1" +isless(a::Number, b::MultiValue) = all(isless.(a, b.data)) +function <=(a::Number, b::MultiValue) + all(a .<= b.data) # default doesen't work because a==b is always false +end +isless(a::MultiValue, b::MultiValue) = @unreachable "Comparison is not defined between tensor of order greater than 1" +function <=(a::MultiValue, b::MultiValue) + isless(a,b) || isequal(a,b) +end ############################################################### -# Addition / subtraction +# promotion and conversions ############################################################### -Base.iszero(a::MultiValue) = all(iszero.(a.data)) +# No default promotion /conversion for tensors of different type names +promote_rule(::Type{<:MultiValue}, ::Type{<:MultiValue}) = Union{} -for op in (:+,:-) - @eval begin +# But promotion and conversion between the different types of square tensors. +promote_rule(::Type{<:TensorValue{D,D,Ta}}, ::Type{<:SymTensorValue{D,Tb}}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)} +promote_rule(::Type{<:TensorValue{D,D,Ta}}, ::Type{<:SkewSymTensorValue{D,Tb}}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)} +promote_rule(::Type{<:TensorValue{D,D,Ta}}, ::Type{<:SymTracelessTensorValue{D,Tb}}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)} +promote_rule(::Type{<:SymTensorValue{D,Ta}}, ::Type{<:SkewSymTensorValue{D,Tb}}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)} +promote_rule(::Type{<:SymTensorValue{D,Ta}}, ::Type{<:SymTracelessTensorValue{D,Tb}}) where {D,Ta,Tb} = SymTensorValue{D,promote_type(Ta,Tb)} +promote_rule(::Type{<:SkewSymTensorValue{D,Ta}},::Type{<:SymTracelessTensorValue{D,Tb}}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)} - function ($op)(a::T) where {T<:MultiValue} - r = map($op, a.data) - T(r) - end - - function ($op)(a::MultiValue,b::MultiValue) - @notimplemented "Not implemented or undefined operation \"$($op)\" on MultiValues of these shapes" - end - - function ($op)(a::MultiValue{S},b::MultiValue{S}) where S - r = map(($op), a.data, b.data) - T = _eltype($op,r,a,b) - M = change_eltype(a,T) - M(r) - end +convert(::Type{<:TensorValue{D,D,Ta}}, a::SymTensorValue{D,Tb}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)}(get_array(a)) +convert(::Type{<:TensorValue{D,D,Ta}}, a::SkewSymTensorValue{D,Tb}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)}(get_array(a)) +convert(::Type{<:TensorValue{D,D,Ta}}, a::SymTracelessTensorValue{D,Tb}) where {D,Ta,Tb} = TensorValue{D,D,promote_type(Ta,Tb)}(get_array(a)) +convert(::Type{<:SymTensorValue{D,Ta}}, a::SymTracelessTensorValue{D,Tb}) where {D,Ta,Tb} = SymTensorValue{D,promote_type(Ta,Tb)}(a.data) - function ($op)(a::TensorValue{D,D},b::SymTensorValue{D}) where D - map(($op), a, TensorValue(get_array(b))) - end +""" + const _Scalar = Union{Real,Complex} - function ($op)(a::SymTensorValue{D},b::TensorValue{D,D}) where D - map(($op), TensorValue(get_array(a)), b) - end +Abstract type for the scalar types that `MultiValue` support component-wise +operations with. +""" +const _Scalar = Union{Real,Complex} - function ($op)(a::TensorValue{D,D},b::SymTracelessTensorValue{D}) where D - map(($op), a, TensorValue(get_array(b))) - end +# TODO deprecate next two methods ? A few stuff depend on this behavior but very ugly. +function convert(V::Type{<:MultiValue}, a::T) where T<:_Scalar + isone(length(V)) && isone(num_indep_components(V)) || error("Cannot convert value of type $V to type $T") + V(a) +end +function convert(T::Type{<:_Scalar}, a::V) where V<:MultiValue + isone(length(a)) || error("Cannot convert value of type $V to type $T") + T(a[1]) +end - function ($op)(a::SymTracelessTensorValue{D},b::TensorValue{D,D}) where D - map(($op), TensorValue(get_array(a)), b) - end +############################################################### +# Addition / subtraction +############################################################### - function ($op)(a::SymTracelessTensorValue{D},b::SymTensorValue{D}) where D - r = map(($op), a.data, b.data) - T = _eltype($op,r,a,b) - M = change_eltype(b,T) - M(r) - end +Base.iszero(a::MultiValue) = all(iszero.(a.data)) - function ($op)(a::SymTensorValue{D},b::SymTracelessTensorValue{D}) where D - r = map(($op), a.data, b.data) - T = _eltype($op,r,a,b) - M = change_eltype(a,T) - M(r) - end +for op in (:+,:-) + @eval begin - function ($op)(a::SymTracelessTensorValue) - r = map($op, a.data[1:end-1]) - typeof(a)(r) + function ($op)(a::T) where T<:MultiValue + Li = num_indep_components(T) + r = map($op, a.data[1:Li]) + T(r) end - function ($op)(a::SymTracelessTensorValue{D},b::SymTracelessTensorValue{D}) where D - r = map(($op), a.data[1:end-1], b.data[1:end-1]) - T = _eltype($op,r,a,b) - M = change_eltype(a,T) - M(r) + function ($op)(a::V, b::V) where V<:MultiValue + Li = num_indep_components(V) + r = map(($op), a.data[1:Li], b.data[1:Li]) + V(r) end - end end - ############################################################### # Matrix Division ############################################################### function (\)(a::MultiValue{Tuple{D,D}} where D, b::MultiValue) r = get_array(a) \ get_array(b) - T = change_eltype(b,eltype(r)) + T = change_eltype(b, eltype(r)) T(r) end @@ -116,7 +113,7 @@ end # Operations with other numbers ############################################################### -@generated function _bc(f,a::NTuple{N},b::Number) where N +@generated function _bc(f, a::NTuple{N}, b::Number) where N s = "(" for i in 1:N s *= "f(a[$i],b), " @@ -125,7 +122,7 @@ end Meta.parse(s) end -@generated function _bc(f,b::Number,a::NTuple{N}) where N +@generated function _bc(f, b::Number, a::NTuple{N}) where N s = "(" for i in 1:N s *= "f(b,a[$i]), " @@ -136,81 +133,64 @@ end for op in (:+,:-,:*) @eval begin - function ($op)(a::MultiValue,b::Number) - r = _bc($op,a.data,b) - T = _eltype($op,r,a,b) - M = change_eltype(a,T) + function ($op)(a::MultiValue, b::_Scalar) + Li = num_indep_components(a) + r = _bc($op, a.data[1:Li], b) + T = _eltype($op, r, a, b) + M = change_eltype(a, T) M(r) end - function ($op)(a::Number,b::MultiValue) - r = _bc($op,a,b.data) - T = _eltype($op,r,a,b) - M = change_eltype(b,T) + function ($op)(a::_Scalar, b::MultiValue) + Li = num_indep_components(b) + r = _bc($op, a, b.data[1:Li]) + T = _eltype($op, r, a, b) + M = change_eltype(b, T) M(r) end end end -function (*)(a::Number,b::SymTracelessTensorValue) - r = _bc(*,a,b.data[1:end-1]) - T = _eltype(*,r,a,b) - M = change_eltype(b,T) - M(r) -end - -function (*)(a::SymTracelessTensorValue,b::Number) - b*a -end +const _AbstractTracelessTensor{D} = Union{SymTracelessTensorValue{D},SkewSymTensorValue{D}} +_err = "This operation is undefined for traceless tensors" +(+)(::_AbstractTracelessTensor, ::_Scalar) = error(_err) +(+)(::_Scalar, ::_AbstractTracelessTensor) = error(_err) +(-)(::_AbstractTracelessTensor, ::_Scalar) = error(_err) +(-)(::_Scalar, ::_AbstractTracelessTensor) = error(_err) -function (/)(a::MultiValue,b::Number) - r = _bc(/,a.data,b) - T = _eltype(/,r,a,b) - P = change_eltype(a,T) +function (/)(a::MultiValue, b::_Scalar) + Li = num_indep_components(a) + r = _bc(/, a.data[1:Li], b) + T = _eltype(/, r, a, b) + P = change_eltype(a, T) P(r) end -function (/)(a::SymTracelessTensorValue,b::Number) - r = _bc(/,a.data[1:end-1],b) - T = _eltype(/,r,a,b) - M = change_eltype(a,T) - M(r) -end - -const _err = " with number is undefined for traceless tensors" -function +(::SymTracelessTensorValue,::Number) error("Addition" *_err) end -function -(::SymTracelessTensorValue,::Number) error("Subtraction"*_err) end -function +(::Number,::SymTracelessTensorValue) error("Addition" *_err) end -function -(::Number,::SymTracelessTensorValue) error("Subtraction"*_err) end -function +(::SymTracelessTensorValue,::MultiValue) error("Addition" *_err) end -function -(::SymTracelessTensorValue,::MultiValue) error("Subtraction"*_err) end -function +(::MultiValue,::SymTracelessTensorValue) error("Addition" *_err) end -function -(::MultiValue,::SymTracelessTensorValue) error("Subtraction"*_err) end - -@inline function _eltype(op,r,a...) +@inline function _eltype(op, r, a) eltype(r) end -@inline function _eltype(op,r::Tuple{},a...) - typeof(reduce(op,zero.(eltype.(a)))) +@inline function _eltype(op, r, a, b) + eltype(r) end -@inline function _eltype(op,r,a,b) +@inline function _eltype(op, r, a...) eltype(r) end -@inline function _eltype(op,r::Tuple{},a,b) - typeof(op(zero(eltype(a)),zero(eltype(b)))) +@inline function _eltype(op, r::Tuple{}, a) + typeof(op(zero(eltype(a)))) end -@inline function _eltype(op,r,a) - eltype(r) +@inline function _eltype(op, r::Tuple{}, a, b) + typeof(op(zero(eltype(a)), zero(eltype(b)))) end -@inline function _eltype(op,r::Tuple{},a) - typeof(op(zero(eltype(a)))) +@inline function _eltype(op, r::Tuple{}, a...) + typeof(reduce(op, zero.(eltype.(a)))) end + ############################################################### # Dot product (simple contraction) ############################################################### @@ -223,59 +203,55 @@ function (*)(a::MultiValue, b::MultiValue) error(msg) end -# Resolution of silly method ambiguity -const _msg = "Use use simple contraction dot aka ⋅ (\\cdot) or full contraction inner aka ⊙ (\\odot)" -function *(::MultiValue,::SymTracelessTensorValue) @unreachable _msg end -function *(::SymTracelessTensorValue,::MultiValue) @unreachable _msg end -function *(::SymTracelessTensorValue,::AbstractSymTensorValue) @unreachable _msg end -function *(::SymTracelessTensorValue,::SymTracelessTensorValue) @unreachable _msg end - -dot(a::MultiValue{Tuple{D}}, b::MultiValue{Tuple{D}}) where D = inner(a,b) +dot(a::MultiValue{Tuple{D}}, b::MultiValue{Tuple{D}}) where D = inner(a, b) """ dot(a::MultiValue{Tuple{...,D}}, b::MultiValue{Tuple{D,...}}) a ⋅¹ b a ⋅ b -Inner product of two tensors `a` and `b`, that is the single contraction of the last index of `a` with the first index of `b`. The corresponding dimensions `D` must match. No symmetry is preserved. +Single contraction of two tensors `a` and `b`, of the last index of `a` with +the first index of `b`. The corresponding dimensions `D` must match. No symmetry +is preserved. +On two vectors, this is the same as the inner product. """ -dot(a::MultiValue,b::MultiValue) = @notimplemented +dot(a::MultiValue, b::MultiValue) = @notimplemented -@generated function dot(a::MultiValue{Tuple{D1},Ta},b::MultiValue{Tuple{D1,D2},Tb}) where {D1,D2,Ta,Tb} - iszero(length(b)) && return :( zero(VectorValue{D2,$(promote_type(Ta,Tb))}) ) +@generated function dot(a::MultiValue{Tuple{D1},Ta}, b::MultiValue{Tuple{D1,D2},Tb}) where {D1,D2,Ta,Tb} + iszero(length(b)) && return :(zero(VectorValue{D2,$(promote_type(Ta, Tb))})) ss = String[] for j in 1:D2 s = "" for i in 1:D1 s *= "a[$i]*b[$i,$j]+" end - push!(ss,s[1:(end-1)]*", ") + push!(ss, s[1:(end-1)] * ", ") end str = join(ss) Meta.parse("VectorValue{$D2}($str)") end -@generated function dot(a::MultiValue{Tuple{D1,D2},Ta},b::MultiValue{Tuple{D2},Tb}) where {D1,D2,Ta,Tb} - iszero(length(a)) && return :( zero(VectorValue{D1,$(promote_type(Ta,Tb))}) ) +@generated function dot(a::MultiValue{Tuple{D1,D2},Ta}, b::MultiValue{Tuple{D2},Tb}) where {D1,D2,Ta,Tb} + iszero(length(a)) && return :(zero(VectorValue{D1,$(promote_type(Ta, Tb))})) ss = String[] for i in 1:D1 s = "" for j in 1:D2 s *= "a[$i,$j]*b[$j]+" end - push!(ss,s[1:(end-1)]*", ") + push!(ss, s[1:(end-1)] * ", ") end str = join(ss) Meta.parse("VectorValue{$D1}($str)") end @generated function dot(a::MultiValue{Tuple{D1,D3},Ta}, b::MultiValue{Tuple{D3,D2},Tb}) where {D1,D2,D3,Ta,Tb} - (iszero(length(a)) || iszero(length(b))) && return :( zero(TensorValue{D1,D2,$(promote_type(Ta,Tb))}) ) + (iszero(length(a)) || iszero(length(b))) && return :(zero(TensorValue{D1,D2,$(promote_type(Ta, Tb))})) ss = String[] for j in 1:D2 for i in 1:D1 - s = join([ "a[$i,$k]*b[$k,$j]+" for k in 1:D3]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$k]*b[$k,$j]+" for k in 1:D3]) + push!(ss, s[1:(end-1)] * ", ") end end str = join(ss) @@ -284,15 +260,15 @@ end # a_ij = b_ijk*c_k @generated function dot(a::MultiValue{Tuple{D1,D2,D3},Ta}, b::MultiValue{Tuple{D3},Tb}) where {D1,D2,D3,Ta,Tb} - iszero(length(a)) && return :( zero(TensorValue{D1,D2,$(promote_type(Ta,Tb))}) ) - T = promote_type(Ta,Tb) - (iszero(D1) || iszero(D2)) && return :( TensorValue{D1,D2,$T}() ) - iszero(D3) && return :( zero(TensorValue{D1,D2,$T}) ) + iszero(length(a)) && return :(zero(TensorValue{D1,D2,$(promote_type(Ta, Tb))})) + T = promote_type(Ta, Tb) + (iszero(D1) || iszero(D2)) && return :(TensorValue{D1,D2,$T}()) + iszero(D3) && return :(zero(TensorValue{D1,D2,$T})) ss = String[] for j in 1:D2 for i in 1:D1 - s = join([ "a[$i,$j,$k]*b[$k]+" for k in 1:D3]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$j,$k]*b[$k]+" for k in 1:D3]) + push!(ss, s[1:(end-1)] * ", ") end end str = join(ss) @@ -302,14 +278,14 @@ end # a_ijl = b_ijk*c_kl @generated function dot(a::MultiValue{Tuple{D1,D2,D3},Ta}, b::MultiValue{Tuple{D3,D4},Tb}) where {D1,D2,D3,D4,Ta,Tb} (iszero(length(a)) || iszero(length(b))) && return :( - zero(ThirdOrderTensorValue{D1,D2,D4,$(promote_type(Ta,Tb))}) + zero(ThirdOrderTensorValue{D1,D2,D4,$(promote_type(Ta, Tb))}) ) ss = String[] for l in 1:D4 for j in 1:D2 for i in 1:D1 - s = join([ "a[$i,$j,$k]*b[$k,$l]+" for k in 1:D3]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$j,$k]*b[$k,$l]+" for k in 1:D3]) + push!(ss, s[1:(end-1)] * ", ") end end end @@ -319,12 +295,12 @@ end # a_ij = c_k*b_kij @generated function dot(a::MultiValue{Tuple{D1},Ta}, b::MultiValue{Tuple{D1,D2,D3},Tb}) where {D1,D2,D3,Ta,Tb} - iszero(length(b)) && return :( zero(TensorValue{D2,D3,$(promote_type(Ta,Tb))}) ) + iszero(length(b)) && return :(zero(TensorValue{D2,D3,$(promote_type(Ta, Tb))})) ss = String[] for k in 1:D3 for j in 1:D2 - s = join([ "a[$i]*b[$i,$j,$k]+" for i in 1:D1]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i]*b[$i,$j,$k]+" for i in 1:D1]) + push!(ss, s[1:(end-1)] * ", ") end end str = join(ss) @@ -332,16 +308,16 @@ end end # a_ilm = b_ij*c_jlm -@generated function dot(a::MultiValue{Tuple{D1,D2},Ta},b::MultiValue{Tuple{D2,D3,D4},Tb}) where {D1,D2,D3,D4,Ta,Tb} +@generated function dot(a::MultiValue{Tuple{D1,D2},Ta}, b::MultiValue{Tuple{D2,D3,D4},Tb}) where {D1,D2,D3,D4,Ta,Tb} (iszero(length(a)) || iszero(length(b))) && return :( - zero(ThirdOrderTensorValue{D1,D3,D4,$(promote_type(Ta,Tb))}) + zero(ThirdOrderTensorValue{D1,D3,D4,$(promote_type(Ta, Tb))}) ) ss = String[] for m in 1:D4 for l in 1:D3 for i in 1:D1 - s = join([ "a[$i,$j]*b[$j,$l,$m]+" for j in 1:D2]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$j]*b[$j,$l,$m]+" for j in 1:D2]) + push!(ss, s[1:(end-1)] * ", ") end end end @@ -355,7 +331,7 @@ const ⋅¹ = dot # Inner product (full contraction) ############################################################### -inner(a::Number,b::Number) = a*b +inner(a::_Scalar, b::_Scalar) = a * b """ inner(a::MultiValue{S}, b::MultiValue{S}) -> scalar @@ -368,13 +344,13 @@ function inner(a::MultiValue, b::MultiValue) end @generated function inner(a::MultiValue{S,Ta}, b::MultiValue{S,Tb}) where {S,Ta,Tb} - iszero(length(a)) && return :( zero($(promote_type(Ta,Tb))) ) - str = join([" a[$i]*b[$i] +" for i in 1:length(a) ]) + iszero(length(a)) && return :(zero($(promote_type(Ta, Tb)))) + str = join([" a[$i]*b[$i] +" for i in 1:length(a)]) Meta.parse(str[1:(end-1)]) end @generated function inner(a::AbstractSymTensorValue{D,Ta}, b::AbstractSymTensorValue{D,Tb}) where {D,Ta,Tb} - iszero(D) && return :( zero($(promote_type(Ta,Tb))) ) + iszero(D) && return :(zero($(promote_type(Ta, Tb)))) str = "" for i in 1:D str *= "+ a[$i,$i]*b[$i,$i]" @@ -389,10 +365,38 @@ end Meta.parse(str) end -function inner(a::MultiValue{Tuple{D,D,D,D}}, b::MultiValue{Tuple{D,D,D,D}}) where D - double_contraction(a,b) +@generated function inner(a::SymFourthOrderTensorValue{D,Ta}, b::SymFourthOrderTensorValue{D,Tb}) where {D,Ta,Tb} + iszero(D) && return :(zero($(promote_type(Ta, Tb)))) + + S = Tuple{D,D,D,D} + VInt = change_eltype(a, Int) + # each independent component appear either 1,2 of 4 times in inputs + strs = Dict(1 => "(", 2 => "2*(", 4 => "4*(") + + # for each independent component, add its product in the sum of the + # corresponding multiplicative factor + for (i, fi) in enumerate(component_basis(VInt)) + factor = @invoke inner(fi::MultiValue{S,Int}, fi::MultiValue{S,Int}) # use the generic but slower method + strs[factor] *= "indep_comp_getindex(a,$i) * indep_comp_getindex(b,$i) +" + end + + str = string(strs[1][1:(end-1)], ") + ", strs[2][1:(end-1)], ") + ", strs[4][1:(end-1)], ")") + Meta.parse(str) +end + +function inner(a::SkewSymTensorValue{D,Ta}, b::SkewSymTensorValue{D,Tb}) where {D,Ta,Tb} + iszero(D) && return zero(promote_type(Ta, Tb)) + 2 * inner(VectorValue(a.data), VectorValue(b.data)) end +function inner(a::SkewSymTensorValue{D,Ta}, b::AbstractSymTensorValue{D,Tb}) where {D,Ta,Tb} + zero(promote_type(Ta,Tb)) +end +function inner(a::AbstractSymTensorValue{D,Tb}, b::SkewSymTensorValue{D,Ta}) where {D,Ta,Tb} + zero(promote_type(Ta,Tb)) +end + +# TODO These two methods make no sense and shold be removed function inner(a::MultiValue{Tuple{D,D,D,D}}, b::MultiValue{Tuple{D,D}}) where D double_contraction(a,b) end @@ -425,7 +429,7 @@ function double_contraction(a::MultiValue{S1}, b::MultiValue{S2}) where {S1<:Tup @unreachable "Double contraction is only define for tensors of order more than 2, got $L1 and $L2." end - D1, E1, D2, E2 = S1.types[end-1], S1.types[end], S2.types[1], S2.types[2] + D1, E1, D2, E2 = S1.types[end-1], S1.types[end], S2.types[1], S2.types[2] if D1 != D2 || E1 != E2 throw(DimensionMismatch("the last two dimensions of the first argument must match the first two of the second argument, got ($D1,$E1) ≠ ($D2,$E2).")) end @@ -438,24 +442,24 @@ function double_contraction(a::MultiValue{S}, b::MultiValue{S}) where {S<:Tuple{ end # c_i = a_ijk*b_jk -@generated function double_contraction(a::MultiValue{Tuple{D1,D2,D3},Ta}, b::MultiValue{Tuple{D2,D3},Tb}) where {D1,D2,D3,Ta,Tb} - iszero(length(a)) && return :( zero(VectorValue{D1,$(promote_type(Ta,Tb))}) ) +@generated function double_contraction(a::MultiValue{Tuple{D1,D2,D3},Ta}, b::MultiValue{Tuple{D2,D3},Tb}) where {D1,D2,D3,Ta,Tb} + iszero(length(a)) && return :(zero(VectorValue{D1,$(promote_type(Ta, Tb))})) ss = String[] for i in 1:D1 - s = join([ "a[$i,$j,$k]*b[$j,$k]+" for j in 1:D2 for k in 1:D3]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$j,$k]*b[$j,$k]+" for j in 1:D2 for k in 1:D3]) + push!(ss, s[1:(end-1)] * ", ") end str = join(ss) Meta.parse("VectorValue{$D1}(($str))") end # c_k = a_ij*b_ijk -@generated function double_contraction(a::MultiValue{Tuple{D1,D2},Ta}, b::MultiValue{Tuple{D1,D2,D3},Tb}) where {D1,D2,D3,Ta,Tb} - iszero(length(b)) && return :( zero(VectorValue{D3,$(promote_type(Ta,Tb))}) ) +@generated function double_contraction(a::MultiValue{Tuple{D1,D2},Ta}, b::MultiValue{Tuple{D1,D2,D3},Tb}) where {D1,D2,D3,Ta,Tb} + iszero(length(b)) && return :(zero(VectorValue{D3,$(promote_type(Ta, Tb))})) ss = String[] for k in 1:D3 - s = join([ "a[$i,$j]*b[$i,$j,$k]+" for i in 1:D1 for j in 1:D2]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$j]*b[$i,$j,$k]+" for i in 1:D1 for j in 1:D2]) + push!(ss, s[1:(end-1)] * ", ") end str = join(ss) Meta.parse("VectorValue{$D3}(($str))") @@ -465,14 +469,16 @@ end @generated function double_contraction(a::SymFourthOrderTensorValue{3}, b::SymFourthOrderTensorValue{3}) Sym4TensorIndexing = [1111, 1121, 1131, 1122, 1132, 1133, 2111, 2121, 2131, 2122, 2132, 2133, - 3111, 3121, 3131, 3122, 3132, 3133, 2211, 2221, 2231, 2222, 2232, 2233, - 2311, 2321, 2331, 2322, 2332, 2333, 3311, 3321, 3331, 3322, 3332, 3333] + 3111, 3121, 3131, 3122, 3132, 3133, 2211, 2221, 2231, 2222, 2232, 2233, + 2311, 2321, 2331, 2322, 2332, 2333, 3311, 3321, 3331, 3322, 3332, 3333] ss = String[] for off_index in Sym4TensorIndexing - i = parse(Int,string(off_index)[1]); j = parse(Int,string(off_index)[2]); - m = parse(Int,string(off_index)[3]); p = parse(Int,string(off_index)[4]); - s = join([ "a[$i,$j,$k,$l]*b[$k,$l,$m,$p]+" for k in 1:3 for l in 1:3]) - push!(ss,s[1:(end-1)]*", ") + i = parse(Int, string(off_index)[1]) + j = parse(Int, string(off_index)[2]) + m = parse(Int, string(off_index)[3]) + p = parse(Int, string(off_index)[4]) + s = join(["a[$i,$j,$k,$l]*b[$k,$l,$m,$p]+" for k in 1:3 for l in 1:3]) + push!(ss, s[1:(end-1)] * ", ") end str = join(ss) Meta.parse("SymFourthOrderTensorValue{3}($str)") @@ -480,7 +486,7 @@ end # c_ijpm = a_ijkl*b_klpm (general case) @generated function double_contraction(a::SymFourthOrderTensorValue{D,Ta}, b::SymFourthOrderTensorValue{D,Tb}) where {D,Ta,Tb} - iszero(D) && return :( SymFourthOrderTensorValue{0,$(promote_type(Ta,Tb))}() ) + iszero(D) && return :(SymFourthOrderTensorValue{0,$(promote_type(Ta, Tb))}()) str = "" for j in 1:D for i in j:D @@ -492,7 +498,7 @@ end s *= " a[$i,$j,$k,$l]*b[$k,$l,$p,$m] +" end end - str *= s[1:(end-1)]*", " + str *= s[1:(end-1)] * ", " end end end @@ -501,14 +507,14 @@ end end # c_ilm = a_ijk*b_jklm -@generated function double_contraction(a::ThirdOrderTensorValue{D1,D,D,Ta},b::SymFourthOrderTensorValue{D,Tb}) where {D1,D,Ta,Tb} - iszero(length(a)) && return :( zero(ThirdOrderTensorValue{D1,D,D,$(promote_type(Ta,Tb))}) ) +@generated function double_contraction(a::ThirdOrderTensorValue{D1,D,D,Ta}, b::SymFourthOrderTensorValue{D,Tb}) where {D1,D,Ta,Tb} + iszero(length(a)) && return :(zero(ThirdOrderTensorValue{D1,D,D,$(promote_type(Ta, Tb))})) ss = String[] for m in 1:D for l in 1:D for i in 1:D1 - s = join([ "a[$i,$j,$k]*b[$j,$k,$l,$m]+" for j in 1:D for k in 1:D]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$j,$k]*b[$j,$k,$l,$m]+" for j in 1:D for k in 1:D]) + push!(ss, s[1:(end-1)] * ", ") end end end @@ -518,7 +524,7 @@ end # c_ij = a_ijkl*b_kl @generated function double_contraction(a::SymFourthOrderTensorValue{D,Ta}, b::AbstractSymTensorValue{D,Tb}) where {D,Ta,Tb} - iszero(D) && return :( zero(SymTensorValue{D,$(promote_type(Ta,Tb))}) ) + iszero(D) && return :(zero(SymTensorValue{D,$(promote_type(Ta, Tb))})) str = "" for i in 1:D for j in i:D @@ -539,7 +545,7 @@ end # c_kl = a_ij*b_ijkl @generated function double_contraction(a::AbstractSymTensorValue{D,Ta}, b::SymFourthOrderTensorValue{D,Tb}) where {D,Ta,Tb} - iszero(D) && return :( zero(SymTensorValue{D,$(promote_type(Ta,Tb))}) ) + iszero(D) && return :(zero(SymTensorValue{D,$(promote_type(Ta, Tb))})) str = "" for k in 1:D for l in k:D @@ -559,26 +565,26 @@ end end # c_ij = a_ijkl*b_kl -function double_contraction(a::SymFourthOrderTensorValue{D}, b::MultiValue{Tuple{D,D}}) where D - double_contraction(a,symmetric_part(b)) +function double_contraction(a::SymFourthOrderTensorValue{D}, b::MultiValue{Tuple{D,D}}) where {D} + double_contraction(a, symmetric_part(b)) end # c_kl = a_ij*b_ijkl -function double_contraction(a::MultiValue{Tuple{D,D}}, b::SymFourthOrderTensorValue{D}) where D - double_contraction(symmetric_part(a),b) +function double_contraction(a::MultiValue{Tuple{D,D}}, b::SymFourthOrderTensorValue{D}) where {D} + double_contraction(symmetric_part(a), b) end # c_il = a_ijk*b_jkl -@generated function double_contraction(a::ThirdOrderTensorValue{D1,D,E,Ta},b::ThirdOrderTensorValue{D,E,D2,Tb}) where {D1,D,E,D2,Ta,Tb} +@generated function double_contraction(a::ThirdOrderTensorValue{D1,D,E,Ta}, b::ThirdOrderTensorValue{D,E,D2,Tb}) where {D1,D,E,D2,Ta,Tb} (iszero(length(a)) || iszero(length(b))) && return :( - zero(TensorValue{D1,D2,$(promote_type(Ta,Tb))}) + zero(TensorValue{D1,D2,$(promote_type(Ta, Tb))}) ) ss = String[] for l in 1:D2 for i in 1:D1 - s = join([ "a[$i,$j,$k]*b[$j,$k,$l]+" for j in 1:D for k in 1:E]) - push!(ss,s[1:(end-1)]*", ") + s = join(["a[$i,$j,$k]*b[$j,$k,$l]+" for j in 1:D for k in 1:E]) + push!(ss, s[1:(end-1)] * ", ") end end str = join(ss) @@ -587,11 +593,39 @@ end const ⋅² = double_contraction +############################################################### +# Congruent product +############################################################### + +""" + congruent_prod(a, b) + +Given a square second order tensors `a` and a `b`, return `b`ᵀ⋅`a`⋅`b`. +The type of the resulting value is (skew) symmetric stable w.r.t. `typeof(a)`. +""" +function congruent_prod(a::MultiValue{Tuple{D,D},Ta}, b::MultiValue{Tuple{D,D1},Tb}) where {D,D1,Ta,Tb} + T = promote_type(Ta, Tb) + V = _congruent_ret_type(a, D1) + (iszero(D) || iszero(D1)) && return zero(V{T}) + V{T}(get_array(transpose(b) ⋅ a ⋅ b)) +end +_congruent_ret_type(a, D1) = TensorValue{D1,D1} +_congruent_ret_type(a::AbstractSymTensorValue, D1) = SymTensorValue{D1} +_congruent_ret_type(a::SkewSymTensorValue, D1) = SkewSymTensorValue{D1} + +function congruent_prod(a::Number, b::Number) + msg = """ operation only defined for 2nd order tensors `a` and `b` with + `size(b,1) == size(a, 1) == size(a, 2)`, got `size(a)=$(size(a))` and + `size(b)=$(size(b))`. + """ + @unreachable msg +end + ############################################################### # Reductions ############################################################### -for op in (:sum,:maximum,:minimum) +for op in (:sum, :maximum, :minimum) @eval begin $op(a::MultiValue) = $op(a.data) end @@ -599,10 +633,10 @@ end # Outer product (aka dyadic product) -outer(a::Number,b::Number) = a*b +outer(a::Number, b::Number) = a * b -outer(a::MultiValue,b::Number) = a*b -outer(a::Number,b::MultiValue) = a*b +outer(a::MultiValue, b::Number) = a * b +outer(a::Number, b::MultiValue) = a * b """ outer(a,b) @@ -612,36 +646,36 @@ Outer product (or tensor-product) of two `Number`s and/or `MultiValue`s, that is `(a⊗b)[i₁,...,iₙ,j₁,...,jₙ] = a[i₁,...,iₙ]*b[j₁,...,jₙ]`. This falls back to standard multiplication if `a` or `b` is a scalar. """ -function outer(a::MultiValue,b::MultiValue) - @notimplemented +function outer(a::MultiValue, b::MultiValue) + @notimplemented end -@generated function outer(a::MultiValue{Tuple{D},Ta},b::MultiValue{Tuple{Z},Tb}) where {D,Z,Ta,Tb} +@generated function outer(a::MultiValue{Tuple{D},Ta}, b::MultiValue{Tuple{Z},Tb}) where {D,Z,Ta,Tb} (iszero(D) || iszero(Z)) && return :( - zero(TensorValue{D,Z,$(promote_type(Ta,Tb))}) + zero(TensorValue{D,Z,$(promote_type(Ta, Tb))}) ) str = join(["a[$i]*b[$j], " for j in 1:Z for i in 1:D]) Meta.parse("TensorValue{$D,$Z}($str)") end -@generated function outer(a::MultiValue{Tuple{D},Ta},b::MultiValue{Tuple{D1,D2},Tb}) where {D,D1,D2,Ta,Tb} +@generated function outer(a::MultiValue{Tuple{D},Ta}, b::MultiValue{Tuple{D1,D2},Tb}) where {D,D1,D2,Ta,Tb} (iszero(D) || iszero(length(b))) && return :( - zero(ThirdOrderTensorValue{D,D1,D2,$(promote_type(Ta,Tb))}) + zero(ThirdOrderTensorValue{D,D1,D2,$(promote_type(Ta, Tb))}) ) - str = join(["a[$i]*b[$j,$k], " for k in 1:D2 for j in 1:D1 for i in 1:D]) + str = join(["a[$i]*b[$j,$k], " for k in 1:D2 for j in 1:D1 for i in 1:D]) Meta.parse("ThirdOrderTensorValue{D,D1,D2}($str)") end -@generated function outer(a::MultiValue{Tuple{D1,D2},Ta},b::MultiValue{Tuple{D},Tb}) where {D,D1,D2,Ta,Tb} +@generated function outer(a::MultiValue{Tuple{D1,D2},Ta}, b::MultiValue{Tuple{D},Tb}) where {D,D1,D2,Ta,Tb} (iszero(length(a)) || iszero(D)) && return :( - zero(ThirdOrderTensorValue{D1,D2,D,$(promote_type(Ta,Tb))}) + zero(ThirdOrderTensorValue{D1,D2,D,$(promote_type(Ta, Tb))}) ) - str = join(["a[$i,$j]*b[$k], " for k in 1:D for j in 1:D2 for i in 1:D1]) + str = join(["a[$i,$j]*b[$k], " for k in 1:D for j in 1:D2 for i in 1:D1]) Meta.parse("ThirdOrderTensorValue{D1,D2,D}($str)") end -@generated function outer(a::AbstractSymTensorValue{D,Ta},b::AbstractSymTensorValue{D,Tb}) where {D,Ta,Tb} - iszero(D) && return :( zero(SymFourthOrderTensorValue{D,$(promote_type(Ta,Tb))}) ) +@generated function outer(a::AbstractSymTensorValue{D,Ta}, b::AbstractSymTensorValue{D,Tb}) where {D,Ta,Tb} + iszero(D) && return :(zero(SymFourthOrderTensorValue{D,$(promote_type(Ta, Tb))})) str = "" for i in 1:D for j in i:D @@ -662,21 +696,21 @@ const ⊗ = outer ############################################################### function cross(a::MultiValue{Tuple{3}}, b::MultiValue{Tuple{3}}) - VectorValue{3}(a[2]b[3]-a[3]b[2], a[3]b[1]-a[1]b[3], a[1]b[2]-a[2]b[1]) + VectorValue{3}(a[2]b[3] - a[3]b[2], a[3]b[1] - a[1]b[3], a[1]b[2] - a[2]b[1]) end function cross(a::MultiValue{Tuple{2}}, b::MultiValue{Tuple{2}}) - a[1]b[2]-a[2]b[1] + a[1]b[2] - a[2]b[1] end """ cross(a::VectorValue{3}, b::VectorValue{3}) -> VectorValue{3} - cross(a::VectorValue{2}, b::VectorValue{2}) -> Scalar + cross(a::VectorValue{2}, b::VectorValue{2}) -> scalar a × b Cross product of 2D and 3D vector. """ -cross(a::MultiValue,b::MultiValue) = error("Cross product only defined for R2 and R3 vectors of same dimension") +cross(a::MultiValue, b::MultiValue) = error("Cross product only defined for R2 and R3 vectors of same dimension") ############################################################### # Linear Algebra @@ -688,13 +722,13 @@ cross(a::MultiValue,b::MultiValue) = error("Cross product only defined for R2 an Determinent of square second order tensors. """ det(a::MultiValue{Tuple{D,D}}) where {D} = det(get_array(a)) -det(a::MultiValue)= @unreachable "det undefined for this tensor shape: $(size(a))" +det(a::MultiValue) = @unreachable "det undefined for this tensor shape: $(size(a))" det(a::MultiValue{Tuple{1,1}}) = a[1] function det(a::MultiValue{Tuple{2,2}}) - a_11 = a[1,1]; a_12 = a[1,2] - a_21 = a[2,1]; a_22 = a[2,2] + a_11 = a[1, 1]; a_12 = a[1, 2] + a_21 = a[2, 1]; a_22 = a[2, 2] a_11*a_22 - a_12*a_21 end @@ -706,6 +740,8 @@ function det(a::MultiValue{Tuple{3,3}}) (a_11*a_23*a_32 + a_12*a_21*a_33 + a_13*a_22*a_31) end +det(::SkewSymTensorValue{3,T}) where T = zero(T) + """ inv(a::MultiValue{Tuple{D,D}}) @@ -713,39 +749,61 @@ Inverse of a second order tensor. """ inv(a::MultiValue{Tuple{D,D}}) where D = TensorValue(inv(get_array(a))) -# this has better perf than the D=2,3 specialization below -inv(a::SymTracelessTensorValue{2}) = SymTracelessTensorValue(inv(get_array(a))) +const InverseStableTensorTypes{D} = Union{SymTensorValue{D},SkewSymTensorValue{D}} + +function inv(a::InverseStableTensorTypes{D}) where D + ai = inv(get_array(a)) + T = change_eltype(a, eltype(ai)) + T(ai) +end function inv(a::MultiValue{Tuple{1,1}}) - r = 1/a[1] - T = change_eltype(a,typeof(r)) + r = 1 / a[1] + T = change_eltype(a, typeof(r)) T(r) end function inv(a::MultiValue{Tuple{2,2}}) - c = 1/det(a) - data = (a[2,2]*c, -a[2,1]*c, -a[1,2]*c, a[1,1]*c) - TensorValue{2}(data) + c = 1 / det(a) + data = (a[2, 2] * c, -a[2, 1] * c, -a[1, 2] * c, a[1, 1] * c) + TensorValue{2}(data) end function inv(a::MultiValue{Tuple{3,3}}) - a_11 = a[1,1]; a_12 = a[1,2]; a_13 = a[1,3] - a_21 = a[2,1]; a_22 = a[2,2]; a_23 = a[2,3] - a_31 = a[3,1]; a_32 = a[3,2]; a_33 = a[3,3] - c = 1/det(a) - data = ( - ( a_22*a_33 - a_23*a_32 )*c, - -( a_21*a_33 - a_23*a_31 )*c, - ( a_21*a_32 - a_22*a_31 )*c, - -( a_12*a_33 - a_13*a_32 )*c, - ( a_11*a_33 - a_13*a_31 )*c, - -( a_11*a_32 - a_12*a_31 )*c, - ( a_12*a_23 - a_13*a_22 )*c, - -( a_11*a_23 - a_13*a_21 )*c, - ( a_11*a_22 - a_12*a_21 )*c) - TensorValue{3}(data) + a_11 = a[1,1]; a_12 = a[1,2]; a_13 = a[1,3] + a_21 = a[2,1]; a_22 = a[2,2]; a_23 = a[2,3] + a_31 = a[3,1]; a_32 = a[3,2]; a_33 = a[3,3] + c = 1/det(a) + data = ( + ( a_22*a_33 - a_23*a_32 )*c, + -( a_21*a_33 - a_23*a_31 )*c, + ( a_21*a_32 - a_22*a_31 )*c, + -( a_12*a_33 - a_13*a_32 )*c, + ( a_11*a_33 - a_13*a_31 )*c, + -( a_11*a_32 - a_12*a_31 )*c, + ( a_12*a_23 - a_13*a_22 )*c, + -( a_11*a_23 - a_13*a_21 )*c, + ( a_11*a_22 - a_12*a_21 )*c) + TensorValue{3}(data) +end + +function inv(a::SymTensorValue{2}) + c = 1/det(a) + T = change_eltype(a,typeof(c)) + T(a[2,2]*c, -a[2,1]*c, a[1,1]*c) +end + +inv(::SymTracelessTensorValue{1,T}) where T = TensorValue{1,1}(inv(zero(T))) +function inv(a::SymTracelessTensorValue{2}) + c = -1/det(a) + T = change_eltype(a,typeof(c)) + T(a[1,1]*c, a[2,1]*c) end +inv(::SkewSymTensorValue{1,T}) where T = TensorValue{1,1}(inv(zero(T))) +inv(a::SkewSymTensorValue{2}) = (typeof(a))(-inv(a.data[1])) +inv(a::SkewSymTensorValue{3,T,L}) where {T,L} = SkewSymTensorValue{3,T}(tfill(inv(zero(T)), Val(L))) + """ eigen(a::MultiValue{Tuple{D,D}}) @@ -754,6 +812,7 @@ Eigenvalue decomposition of a square second order tensor. eigen(a::MultiValue{Tuple{D,D}}) where D = eigen(get_array(a)) eigen(a::MultiValue) = @unreachable "eigen undefined for this tensor shape: $(size(a))" + ############################################################### # Measure ############################################################### @@ -764,7 +823,7 @@ eigen(a::MultiValue) = @unreachable "eigen undefined for this tensor shape: $(si Euclidean norm of a vector. """ -meas(a::MultiValue{Tuple{D}}) where D = sqrt(inner(a,a)) +meas(a::MultiValue{Tuple{D}}) where D = sqrt(inner(a, a)) """ meas(J::MultiValue{Tuple{D1,D2}}) @@ -774,8 +833,6 @@ formed by the rows of `J`, that is `sqrt(det(J⋅Jᵀ))`, or `abs(det(J))` if `D This is used to compute the contribution of the Jacobian matrix `J` of a changes of variables in integrals. """ meas(a::MultiValue{Tuple{D,D}}) where D = abs(det(a)) -#meas( ::TensorValue{0,D,T}) where {T,D} = one(T) -#meas( ::MultiValue{Tuple{0,0},T}) where {T} = one(T) function meas(v::MultiValue{Tuple{1,D}}) where D t = VectorValue(v.data) @@ -786,7 +843,7 @@ function meas(v::MultiValue{Tuple{2,3}}) n1 = v[1,2]*v[2,3] - v[1,3]*v[2,2] n2 = v[1,3]*v[2,1] - v[1,1]*v[2,3] n3 = v[1,1]*v[2,2] - v[1,2]*v[2,1] - n = VectorValue(n1,n2,n3) + n = VectorValue(n1, n2, n3) meas(n) end @@ -801,11 +858,12 @@ end Euclidean (2-)norm of `u`, namely `sqrt(inner(u,u))`. """ -@inline norm(u::MultiValue{Tuple{D},<:Real}) where D = sqrt(inner(u,u)) -@inline norm(u::MultiValue{Tuple{D}}) where D = sqrt(real(inner(u,conj(u)))) -@inline norm(u::MultiValue{Tuple{D1,D2},<:Real}) where {D1,D2} = sqrt(inner(u,u)) -@inline norm(u::MultiValue{Tuple{D1,D2}}) where {D1,D2} = sqrt(real(inner(u,conj(u)))) -@inline norm(u::MultiValue{Tuple{0},T}) where T = sqrt(zero(T)) +@inline norm(u::MultiValue{Tuple{D},<:Real}) where D = sqrt(inner(u, u)) +@inline norm(u::MultiValue{Tuple{D}}) where D = sqrt(real(inner(u, conj(u)))) +@inline norm(u::MultiValue{Tuple{D1,D2},<:Real}) where {D1,D2} = sqrt(inner(u, u)) +@inline norm(u::MultiValue{Tuple{D1,D2}}) where {D1,D2} = sqrt(real(inner(u, conj(u)))) +@inline norm(u::MultiValue{Tuple{0},T}) where T<:Real= sqrt(zero(T)) +@inline norm(u::MultiValue{Tuple{0},T}) where T = sqrt(real(zero(T))) ############################################################### # conj, real, imag @@ -813,19 +871,13 @@ Euclidean (2-)norm of `u`, namely `sqrt(inner(u,u))`. for op in (:conj,:real,:imag) @eval begin - function ($op)(a::T) where {T<:MultiValue} - r = map($op, a.data) + function ($op)(a::T) where T<:MultiValue + Li = num_indep_components(a) + r = map($op, a.data[1:Li]) T2 = _eltype($op,r,a) - M = change_eltype(a,T2) + M = change_eltype(a,T2) M(r) end - - function ($op)(a::T) where {T<:SymTracelessTensorValue} - r = map($op, a.data) - T2 = _eltype($op,r,a) - M = change_eltype(a,T2) - M(r[1:end-1]) - end end end @@ -840,10 +892,11 @@ Return the trace of a second order square tensor, defined by `Σᵢ vᵢᵢ` or """ @generated function tr(v::MultiValue{Tuple{D,D},T}) where {D,T} iszero(D) && return :(zero(T)) - str = join([" v[$i,$i] +" for i in 1:D ]) + str = join([" v[$i,$i] +" for i in 1:D]) Meta.parse(str[1:(end-1)]) end tr(::SymTracelessTensorValue{D,T}) where {D,T} = zero(T) +tr(::SkewSymTensorValue{D,T}) where {D,T} = zero(T) tr(::MultiValue{Tuple{A,B}}) where {A,B} = throw(ArgumentError("Second order tensor is not square")) """ @@ -852,11 +905,11 @@ tr(::MultiValue{Tuple{A,B}}) where {A,B} = throw(ArgumentError("Second order ten Return a vector of length `D2` of traces computed on the first two indices: `resⱼ = Σᵢ vᵢᵢⱼ`. """ @generated function tr(v::MultiValue{Tuple{A,A,B},T}) where {A,B,T} - iszero(length(v)) && return :( zero(VectorValue{B,T}) ) + iszero(length(v)) && return :(zero(VectorValue{B,T})) str = "" for k in 1:B for i in 1:A - if i !=1 + if i != 1 str *= " + " end str *= " v[$i,$i,$k]" @@ -878,7 +931,6 @@ transpose(a::MultiValue{Tuple{D,D}}) where D = @notimplemented str = "" for i in 1:D1 for j in 1:D2 - k = (j-1)*D1 + i str *= "conj(a[$i,$j]), " end end @@ -900,53 +952,59 @@ end end adjoint(a::AbstractSymTensorValue) = conj(a) - -@inline adjoint(a::AbstractSymTensorValue{D,T} where {D,T<:Real}) = transpose(a) +adjoint(a::SkewSymTensorValue) = -conj(a) transpose(a::AbstractSymTensorValue) = a +transpose(a::SkewSymTensorValue) = -a ############################################################### -# Symmetric part +# Symmetric and Skew symmetric parts ############################################################### """ symmetric_part(v::MultiValue{Tuple{D,D}})::AbstractSymTensorValue Return the symmetric part of second order tensor, that is `½(v + vᵀ)`. -Return `v` if `v isa AbstractSymTensorValue`. +Return `v` if `v isa AbstractSymTensorValue`, and the zero symmetric tensor if +`v isa SkewSymTensorValue`. """ @generated function symmetric_part(v::MultiValue{Tuple{D,D},T}) where {D,T} - iszero(D) && return :( zero(SymTensorValue{0,T}) ) + iszero(D) && return :(zero(SymTensorValue{0,T})) str = "(" for j in 1:D - for i in j:D - str *= "0.5*v[$i,$j] + 0.5*v[$j,$i], " - end + for i in j:D + str *= "(v[$i,$j] + v[$j,$i])/2, " + end end str *= ")" Meta.parse("SymTensorValue{D}($str)") end symmetric_part(v::AbstractSymTensorValue) = v +symmetric_part(::SkewSymTensorValue{D,T}) where {D,T} = zero(SymTensorValue{D,T}) """ - skew_symmetric_part(v::MultiValue{Tuple{D,D}})::MultiValue{Tuple{D,D}} + skew_symmetric_part(v::MultiValue{Tuple{D,D}})::SkewSymTensorValue{D} -Return the asymmetric part of second order tensor, that is `½(v - vᵀ)`. -Return `v` if `v isa AbstractSymTensorValue`. +Return the asymmetric part of `v`, that is `½(v - vᵀ)`. +Return the zero skew symmetric tensor if `v isa AbstractSymTensorValue`, and +`v` itself if `v isa SkewSymTensorValue`. """ @generated function skew_symmetric_part(v::MultiValue{Tuple{D,D},T}) where {D,T} - iszero(D) && return :( zero(TensorValue{0,0,T}) ) + iszero(D) && return :(zero(SkewSymTensorValue{0,T})) str = "(" - for j in 1:D - for i in 1:D - str *= "0.5*v[$i,$j] - 0.5*v[$j,$i], " - end + for i in 1:D + for j in i+1:D + str *= "(v[$i,$j] - v[$j,$i])/2, " + end end str *= ")" - Meta.parse("TensorValue{D,D}($str)") + Meta.parse("SkewSymTensorValue{D}($str)") end +skew_symmetric_part(::AbstractSymTensorValue{D,T}) where {D,T} = zero(SkewSymTensorValue{D,T}) +skew_symmetric_part(v::SkewSymTensorValue) = v + ############################################################### # diag ############################################################### @@ -955,19 +1013,22 @@ function LinearAlgebra.diag(a::MultiValue{Tuple{D,D},T}) where {D,T} VectorValue{D,T}((a[i,i] for i in 1:D)...) end +function LinearAlgebra.diag(a::SkewSymTensorValue{D,T}) where {D,T} + zero(VectorValue{D,T}) +end + ############################################################### # Broadcast ############################################################### -# TODO more cases need to be added -function Base.broadcasted(f,a::VectorValue,b::VectorValue) - VectorValue(map(f,a.data,b.data)) +function Base.broadcasted(f, a::VectorValue, b::VectorValue) + VectorValue(map(f, a.data, b.data)) end -function Base.broadcasted(f,a::TensorValue,b::TensorValue) - TensorValue(map(f,a.data,b.data)) +function Base.broadcasted(f, a::TensorValue, b::TensorValue) + TensorValue(map(f, a.data, b.data)) end -function Base.broadcasted(f,a::AbstractSymTensorValue,b::AbstractSymTensorValue) - SymTensorValue(map(f,a.data,b.data)) +function Base.broadcasted(f, a::AbstractSymTensorValue, b::AbstractSymTensorValue) + SymTensorValue(map(f, a.data, b.data)) end diff --git a/src/TensorValues/SkewSymTensorValueTypes.jl b/src/TensorValues/SkewSymTensorValueTypes.jl new file mode 100644 index 000000000..49b5f7bcd --- /dev/null +++ b/src/TensorValues/SkewSymTensorValueTypes.jl @@ -0,0 +1,125 @@ +############################################################### +# SkewSymTensorValue Type +############################################################### + +""" + SkewSymTensorValue{D,T,L} <: MultiValue{Tuple{D,D},T,2,L} + +Type representing a skew symmetric second-order `D`×`D` tensor. +It must hold `L` = `D`(`D`-1)/2. + +It is constructed by providing the components of index (i,j) for 1 ≤ i < j ≤ `D`. +""" +struct SkewSymTensorValue{D,T,L} <: MultiValue{Tuple{D,D},T,2,L} + data::NTuple{L,T} + function SkewSymTensorValue{D,T}(data::NTuple{L,T}) where {D,T,L} + @check L == D*(D-1)÷2 + new{D,T,L}(data) + end +end + +function promote_rule(::Type{<:SkewSymTensorValue{D,Ta}}, ::Type{<:SkewSymTensorValue{D,Tb}}) where {D,Ta,Tb} + T = promote_type(Ta,Tb) + SkewSymTensorValue{D,T} +end + +############################################################### +# Constructors (SkewSymTensorValue) +############################################################### + +# Empty SkewSymTensorValue constructor + +SkewSymTensorValue() = SkewSymTensorValue{0,Int}(NTuple{0,Int}()) +SkewSymTensorValue{0}() = SkewSymTensorValue{0,Int}(NTuple{0,Int}()) +SkewSymTensorValue{0,T}() where {T} = SkewSymTensorValue{0,T}(NTuple{0,T}()) +SkewSymTensorValue(data::NTuple{0}) = SkewSymTensorValue{0,Int}(data) +SkewSymTensorValue{0}(data::NTuple{0}) = SkewSymTensorValue{0,Int}(data) + +# 1D SkewSymTensorValue missing constructor + +SkewSymTensorValue{1}() = SkewSymTensorValue{1,Int}(NTuple{0,Int}()) +SkewSymTensorValue{1}(data::NTuple{0}) = SkewSymTensorValue{1,Int}(data) + +# SkewSymTensorValue single NTuple argument constructor + +@generated function SkewSymTensorValue(data::NTuple{L,T}) where {L,T} + msg = "Invalid number of scalar arguments in SkewSymTensorValue constructor" + V = (sqrt(1+8*L)+1)/2 + @check floor(Int,V) == ceil(Int,V) msg + D = Int(V) + quote + SkewSymTensorValue{$D,T}(data) + end +end +SkewSymTensorValue{D}(data::NTuple{L,T}) where {D,L,T} = SkewSymTensorValue{D,T}(data) +SkewSymTensorValue{D,T1}(data::NTuple{L,T2}) where {D,L,T1,T2} = SkewSymTensorValue{D,T1}(NTuple{L,T1}(data)) +SkewSymTensorValue{D,T1,L}(data::NTuple{L,T2}) where {D,L,T1,T2} = SkewSymTensorValue{D,T1}(NTuple{L,T1}(data)) + +# SkewSymTensorValue single Tuple argument constructor + +SkewSymTensorValue(data::Tuple) = SkewSymTensorValue(promote(data...)) +SkewSymTensorValue{D}(data::Tuple) where {D} = SkewSymTensorValue{D}(promote(data...)) +SkewSymTensorValue{D,T1}(data::Tuple) where {D,T1} = SkewSymTensorValue{D,T1}(NTuple{length(data),T1}(data)) +SkewSymTensorValue{D,T1,L}(data::Tuple) where {D,T1,L} = SkewSymTensorValue{D,T1}(NTuple{L,T1}(data)) + +# SkewSymTensorValue Vararg constructor + +SkewSymTensorValue(data::Number...) = SkewSymTensorValue(data) +SkewSymTensorValue{D}(data::Number...) where {D} = SkewSymTensorValue{D}(data) +SkewSymTensorValue{D,T1}(data::Number...) where {D,T1} = SkewSymTensorValue{D,T1}(data) +SkewSymTensorValue{D,T1,L}(data::Number...) where {D,T1,L} = SkewSymTensorValue{D,T1}(data) + +# SkewSymTensorValue single AbstractMatrix argument constructor + +#From Square Matrices +@generated function _flatten_skewsym(data::AbstractArray,::Val{D}) where D + check_e = :( @check ($D,$D) == size(data) ) + str = "" + for i in 1:D + for j in i+1:D + str *= "data[$i,$j], " + end + end + ret_e = Meta.parse(" return ($str)") + Expr(:block, check_e, ret_e) +end + +SkewSymTensorValue(data::AbstractMatrix{T}) where {T} = ((D1,D2)=size(data); SkewSymTensorValue{D1}(data)) +SkewSymTensorValue{D}(data::AbstractMatrix{T}) where {D,T} = SkewSymTensorValue{D,T}(_flatten_skewsym(data,Val{D}())) +SkewSymTensorValue{D,T1}(data::AbstractMatrix{T2}) where {D,T1,T2} = SkewSymTensorValue{D,T1}(_flatten_skewsym(data,Val{D}())) +SkewSymTensorValue{D,T1,L}(data::AbstractMatrix{T2}) where {D,T1,T2,L} = SkewSymTensorValue{D,T1,L}(_flatten_skewsym(data,Val{D}())) + +############################################################### +# Conversions (SkewSymTensorValue) +############################################################### + +@generated function _SkewSymTensorValue_to_array(arg::SkewSymTensorValue{D,T,L}) where {D,T,L} + z = zero(T) + str = "" + for j in 1:D + for i in 1:D + str *= "arg[$i,$j], " + end + end + Meta.parse("SMatrix{D,D,T}(($str))") +end + +# Inverse conversion +convert(::Type{<:MArray{Tuple{D,D},T}}, arg::SkewSymTensorValue{D}) where {D,T} = MMatrix{D,D,T}(_SkewSymTensorValue_to_array(arg)) +convert(::Type{<:SArray{Tuple{D,D},T}}, arg::SkewSymTensorValue{D}) where {D,T} = _SkewSymTensorValue_to_array(arg) + +# Internal conversion +convert(::Type{<:SkewSymTensorValue{D,T}}, arg::SkewSymTensorValue{D}) where {D,T} = SkewSymTensorValue{D,T}(Tuple(arg)) +convert(::Type{<:SkewSymTensorValue{D,T}}, arg::SkewSymTensorValue{D,T}) where {D,T} = arg + +############################################################### +# Other constructors and conversions (SkewSymTensorValue) +############################################################### + +one(::Type{<:SkewSymTensorValue}) = @unreachable "Skew symmetric tensors do not have multiplicative neutral." + +change_eltype(::Type{<:SkewSymTensorValue{D}},::Type{T2}) where {D,T2} = SkewSymTensorValue{D,T2} +change_eltype(::Type{SkewSymTensorValue{D,T1,L}},::Type{T2}) where {D,T1,T2,L} = SkewSymTensorValue{D,T2,L} + +num_indep_components(::Type{<:SkewSymTensorValue{D}}) where {D} = D*(D-1)÷2 + diff --git a/src/TensorValues/SymFourthOrderTensorValueTypes.jl b/src/TensorValues/SymFourthOrderTensorValueTypes.jl index 43a41567a..d74019986 100644 --- a/src/TensorValues/SymFourthOrderTensorValueTypes.jl +++ b/src/TensorValues/SymFourthOrderTensorValueTypes.jl @@ -17,6 +17,11 @@ struct SymFourthOrderTensorValue{D,T,L} <: MultiValue{Tuple{D,D,D,D},T,4,L} end end +function promote_rule(::Type{<:SymFourthOrderTensorValue{D,Ta}}, ::Type{<:SymFourthOrderTensorValue{D,Tb}}) where {D,Ta,Tb} + T = promote_type(Ta,Tb) + SymFourthOrderTensorValue{D,T} +end + ############################################################### # Constructors (SymFourthOrderTensorValue) ############################################################### @@ -56,15 +61,51 @@ SymFourthOrderTensorValue(data::Number...) = SymFourthOrderTensorValue(data) SymFourthOrderTensorValue{D}(data::Number...) where {D} = SymFourthOrderTensorValue{D}(data) SymFourthOrderTensorValue{D,T1}(data::Number...) where {D,T1} = SymFourthOrderTensorValue{D,T1}(data) +# SymFourthOrderTensorValue single AbstractArray argument constructor + +#From square 4-dim Array +@generated function _flatten_sym_fourth_order_tensor(data::AbstractArray,::Val{D}) where D + check_e = :( @check ($D,$D,$D,$D) == size(data) ) + str = "" + for i in 1:D + for j in i:D + for k in 1:D + for l in k:D + str *= "data[$i,$j,$k,$l]," + end + end + end + end + ret_e = Meta.parse("return ($str)") + Expr(:block, check_e, ret_e) +end + +SymFourthOrderTensorValue(data::AbstractArray{T}) where {T} = ((D,)=size(data); SymFourthOrderTensorValue{D}(data)) +SymFourthOrderTensorValue{D}(data::AbstractArray{T}) where {D,T} = SymFourthOrderTensorValue{D,T}(_flatten_sym_fourth_order_tensor(data,Val(D))) +SymFourthOrderTensorValue{D,T1}(data::AbstractArray{T2}) where {D,T1,T2} = SymFourthOrderTensorValue{D,T1}(_flatten_sym_fourth_order_tensor(data,Val(D))) +SymFourthOrderTensorValue{D,T1,L}(data::AbstractArray{T2}) where {D,T1,T2,L} = SymFourthOrderTensorValue{D,T1,L}(_flatten_sym_fourth_order_tensor(data,Val(D))) + ############################################################### # Conversions (SymFourthOrderTensorValue) ############################################################### -# Direct conversion -convert(::Type{<:SymFourthOrderTensorValue{D,T}}, arg::Tuple) where {D,T} = SymFourthOrderTensorValue{D,T}(arg) +@generated function _SymFourthOrder_to_array(arg::SymFourthOrderTensorValue{D,T,L}) where {D,T,L} + str = "" + for l in 1:D + for k in 1:D + for j in 1:D + for i in 1:D + str *= "arg[$i,$j,$k,$l], " + end + end + end + end + Meta.parse("SArray{Tuple{D,D,D,D},T}(($str))") +end # Inverse conversion -convert(::Type{<:NTuple{L,T}}, arg::SymFourthOrderTensorValue) where {L,T} = NTuple{L,T}(Tuple(arg)) +convert(::Type{<:MArray{Tuple{D,D,D,D},T}}, arg::SymFourthOrderTensorValue) where {D,T} = MArray{Tuple{D,D,D,D},T}(_SymFourthOrder_to_array(arg)) +convert(::Type{<:SArray{Tuple{D,D,D,D},T}}, arg::SymFourthOrderTensorValue) where {D,T} = _SymFourthOrder_to_array(arg) # Internal conversion convert(::Type{<:SymFourthOrderTensorValue{D,T}}, arg::SymFourthOrderTensorValue{D}) where {D,T} = SymFourthOrderTensorValue{D,T}(Tuple(arg)) @@ -74,15 +115,6 @@ convert(::Type{<:SymFourthOrderTensorValue{D,T}}, arg::SymFourthOrderTensorValue # Other constructors and conversions (SymFourthOrderTensorValue) ############################################################### -@generated function zero(::Type{<:SymFourthOrderTensorValue{D,T}}) where {D,T} - L=(D*(D+1)÷2)^2 - quote - SymFourthOrderTensorValue{D,T}(tfill(zero(T),Val{$L}())) - end -end -zero(::Type{<:SymFourthOrderTensorValue{D,T,L}}) where {D,T,L} = SymFourthOrderTensorValue{D,T}(tfill(zero(T),Val{L}())) -zero(::SymFourthOrderTensorValue{D,T,L}) where {D,T,L} = zero(SymFourthOrderTensorValue{D,T,L}) - # This is in fact the "symmetrized" 4th order identity """ one(::SymFourthOrderTensorValue{D,T}}) @@ -96,46 +128,11 @@ The scalar type `T2` of the result is `typeof(one(T)/2)`. str = join(["($i==$k && $j==$l) ? ( $i==$j ? one($S) : one($S)/2) : zero($S), " for i in 1:D for j in i:D for k in 1:D for l in k:D]) Meta.parse("SymFourthOrderTensorValue{D,$S}(($str))") end -one(::SymFourthOrderTensorValue{D,T}) where {D,T} = one(SymFourthOrderTensorValue{D,T}) -@generated function rand(rng::AbstractRNG, - ::Random.SamplerType{<:SymFourthOrderTensorValue{D,T}}) where {D,T} - L=(D*(D+1)÷2)^2 - quote - rand(rng, SymFourthOrderTensorValue{D,T,$L}) - end -end -rand(rng::AbstractRNG,::Random.SamplerType{<:SymFourthOrderTensorValue{D,T,L}}) where {D,T,L} = - SymFourthOrderTensorValue{D,T}(Tuple(rand(rng, SVector{L,T}))) - -Mutable(::Type{<:SymFourthOrderTensorValue{D,T}}) where {D,T} = @notimplemented -Mutable(::SymFourthOrderTensorValue{D,T}) where {D,T} = Mutable(SymFourthOrderTensorValue{D,T}) -mutable(a::SymFourthOrderTensorValue{D}) where D = @notimplemented - -change_eltype(::Type{SymFourthOrderTensorValue{D,T1}},::Type{T2}) where {D,T1,T2} = SymFourthOrderTensorValue{D,T2} +change_eltype(::Type{<:SymFourthOrderTensorValue{D}},::Type{T2}) where {D,T2} = SymFourthOrderTensorValue{D,T2} change_eltype(::Type{SymFourthOrderTensorValue{D,T1,L}},::Type{T2}) where {D,T1,T2,L} = SymFourthOrderTensorValue{D,T2,L} -change_eltype(::SymFourthOrderTensorValue{D,T1,L},::Type{T2}) where {D,T1,T2,L} = change_eltype(SymFourthOrderTensorValue{D,T1,L},T2) - -############################################################### -# Introspection (SymFourthOrderTensorValue) -############################################################### - -eltype(::Type{<:SymFourthOrderTensorValue{D,T}}) where {D,T} = T -eltype(::SymFourthOrderTensorValue{D,T}) where {D,T} = eltype(SymFourthOrderTensorValue{D,T}) - -size(::Type{<:SymFourthOrderTensorValue{D}}) where {D} = (D,D,D,D) -size(::SymFourthOrderTensorValue{D}) where {D} = size(SymFourthOrderTensorValue{D}) - -length(::Type{<:SymFourthOrderTensorValue{D}}) where {D} = D*D*D*D -length(::SymFourthOrderTensorValue{D}) where {D} = length(SymFourthOrderTensorValue{D}) - -num_components(::Type{<:SymFourthOrderTensorValue}) = @unreachable "The dimension is needed to count components" -num_components(::Type{<:SymFourthOrderTensorValue{D}}) where {D} = length(SymFourthOrderTensorValue{D}) -num_components(::SymFourthOrderTensorValue{D}) where {D} = num_components(SymFourthOrderTensorValue{D}) -num_indep_components(::Type{<:SymFourthOrderTensorValue}) = num_components(SymFourthOrderTensorValue) num_indep_components(::Type{<:SymFourthOrderTensorValue{D}}) where {D} = (D*(D+1)÷2)^2 -num_indep_components(::SymFourthOrderTensorValue{D}) where {D} = num_indep_components(SymFourthOrderTensorValue{D}) ############################################################### # VTK export (SymFourthOrderTensorValue) diff --git a/src/TensorValues/SymTensorValueTypes.jl b/src/TensorValues/SymTensorValueTypes.jl index 258a9785b..39bf5eec4 100644 --- a/src/TensorValues/SymTensorValueTypes.jl +++ b/src/TensorValues/SymTensorValueTypes.jl @@ -10,6 +10,9 @@ See also [`SymTensorValue`](@ref), [`SymTracelessTensorValue`](@ref). """ abstract type AbstractSymTensorValue{D,T,L} <: MultiValue{Tuple{D,D},T,2,L} end +num_indep_components(::Type{<:AbstractSymTensorValue}) = @unreachable "Concrete + type and dimension are needed to count components of symmetric tensors." + """ SymTensorValue{D,T,L} <: AbstractSymTensorValue{D,T,L} @@ -25,6 +28,11 @@ struct SymTensorValue{D,T,L} <: AbstractSymTensorValue{D,T,L} end end +function promote_rule(::Type{<:SymTensorValue{D,Ta}}, ::Type{<:SymTensorValue{D,Tb}}) where {D,Ta,Tb} + T = promote_type(Ta,Tb) + SymTensorValue{D,T} +end + ############################################################### # Constructors (SymTensorValue) ############################################################### @@ -70,16 +78,18 @@ SymTensorValue{D,T1,L}(data::Number...) where {D,T1,L} = SymTensorValue{D,T1}(da #From Square Matrices @generated function _flatten_upper_triangle(data::AbstractArray,::Val{D}) where D + check_e = :( @check ($D,$D) == size(data) ) str = "" for i in 1:D for j in i:D str *= "data[$i,$j], " end end - Meta.parse("($str)") + ret_e = Meta.parse(" return ($str)") + Expr(:block, check_e, ret_e) end -SymTensorValue(data::AbstractMatrix{T}) where {T} = ((D1,D2)=size(data); SymTensorValue{D1}(data)) +SymTensorValue(data::AbstractMatrix{T}) where {T} = ((D1,)=size(data); SymTensorValue{D1}(data)) SymTensorValue{D}(data::AbstractMatrix{T}) where {D,T} = SymTensorValue{D,T}(_flatten_upper_triangle(data,Val{D}())) SymTensorValue{D,T1}(data::AbstractMatrix{T2}) where {D,T1,T2} = SymTensorValue{D,T1}(_flatten_upper_triangle(data,Val{D}())) SymTensorValue{D,T1,L}(data::AbstractMatrix{T2}) where {D,T1,T2,L} = SymTensorValue{D,T1,L}(_flatten_upper_triangle(data,Val{D}())) @@ -99,14 +109,9 @@ SymTensorValue{D,T1,L}(data::AbstractMatrix{T2}) where {D,T1,T2,L} = SymTensorVa Meta.parse("SMatrix{D,D,T}(($str))") end -# Direct conversion -convert(::Type{<:SymTensorValue{D,T}}, arg::AbstractArray) where {D,T} = SymTensorValue{D,T}(arg) -convert(::Type{<:SymTensorValue{D,T}}, arg::Tuple) where {D,T} = SymTensorValue{D,T}(arg) - # Inverse conversion -convert(::Type{<:MMatrix{D,D,T}}, arg::SymTensorValue) where {D,T} = MMatrix{D,D,T}(_SymTensorValue_to_array(arg)) -convert(::Type{<:SMatrix{D,D,T}}, arg::SymTensorValue) where {D,T} = _SymTensorValue_to_array(arg) -convert(::Type{<:NTuple{L,T}}, arg::SymTensorValue) where {L,T} = NTuple{L,T}(Tuple(arg)) +convert(::Type{<:MArray{Tuple{D,D},T}}, arg::SymTensorValue) where {D,T} = MMatrix{D,D,T}(_SymTensorValue_to_array(arg)) +convert(::Type{<:SArray{Tuple{D,D},T}}, arg::SymTensorValue) where {D,T} = _SymTensorValue_to_array(arg) # Internal conversion convert(::Type{<:SymTensorValue{D,T}}, arg::SymTensorValue{D}) where {D,T} = SymTensorValue{D,T}(Tuple(arg)) @@ -116,62 +121,15 @@ convert(::Type{<:SymTensorValue{D,T}}, arg::SymTensorValue{D,T}) where {D,T} = a # Other constructors and conversions (SymTensorValue) ############################################################### -@generated function zero(::Type{<:SymTensorValue{D,T}}) where {D,T} - L=D*(D+1)÷2 - quote - SymTensorValue{D,T}(tfill(zero(T),Val{$L}())) - end -end - -zero(::Type{<:SymTensorValue{D,T,L}}) where {D,T,L} = SymTensorValue{D,T}(tfill(zero(T),Val{L}())) -zero(::SymTensorValue{D,T,L}) where {D,T,L} = zero(SymTensorValue{D,T,L}) - @generated function one(::Type{<:SymTensorValue{D,T}}) where {D,T} str = join(["$i==$j ? one(T) : zero(T), " for i in 1:D for j in i:D]) Meta.parse("SymTensorValue{D,T}(($str))") end -one(::SymTensorValue{D,T}) where {D,T} = one(SymTensorValue{D,T}) - -@generated function rand(rng::AbstractRNG, - ::Random.SamplerType{<:SymTensorValue{D,T}}) where {D,T} - L=D*(D+1)÷2 - quote - rand(rng, SymTensorValue{D,T,$L}) - end -end -rand(rng::AbstractRNG,::Random.SamplerType{<:SymTensorValue{D,T,L}}) where {D,T,L} = - SymTensorValue{D,T}(Tuple(rand(rng, SVector{L,T}))) - -Mutable(::Type{<:SymTensorValue{D,T}}) where {D,T} = MMatrix{D,D,T} -Mutable(::SymTensorValue{D,T}) where {D,T} = Mutable(SymTensorValue{D,T}) -mutable(a::SymTensorValue{D}) where D = MMatrix{D,D}(Tuple(get_array(a))) -change_eltype(::Type{SymTensorValue{D,T1}},::Type{T2}) where {D,T1,T2} = SymTensorValue{D,T2} +change_eltype(::Type{<:SymTensorValue{D}},::Type{T2}) where {D,T2} = SymTensorValue{D,T2} change_eltype(::Type{SymTensorValue{D,T1,L}},::Type{T2}) where {D,T1,T2,L} = SymTensorValue{D,T2,L} -change_eltype(::SymTensorValue{D,T1,L},::Type{T2}) where {D,T1,T2,L} = change_eltype(SymTensorValue{D,T1,L},T2) - -get_array(arg::SymTensorValue{D,T,L}) where {D,T,L} = convert(SMatrix{D,D,T}, arg) - -############################################################### -# Introspection (SymTensorValue) -############################################################### - -eltype(::Type{<:SymTensorValue{D,T}}) where {D,T} = T -eltype(::SymTensorValue{D,T}) where {D,T} = eltype(SymTensorValue{D,T}) - -size(::Type{<:SymTensorValue{D}}) where {D} = (D,D) -size(::SymTensorValue{D}) where {D} = size(SymTensorValue{D}) - -length(::Type{<:SymTensorValue{D}}) where {D} = D*D -length(::SymTensorValue{D}) where {D} = length(SymTensorValue{D}) - -num_components(::Type{<:SymTensorValue}) = @unreachable "The dimension is needed to count components" -num_components(::Type{<:SymTensorValue{D}}) where {D} = length(SymTensorValue{D}) -num_components(::SymTensorValue{D}) where {D} = num_components(SymTensorValue{D}) -num_indep_components(::Type{<:SymTensorValue}) = num_components(SymTensorValue) num_indep_components(::Type{<:SymTensorValue{D}}) where {D} = D*(D+1)÷2 -num_indep_components(::SymTensorValue{D}) where {D} = num_indep_components(SymTensorValue{D}) ############################################################### # VTK export (SymTensorValue) diff --git a/src/TensorValues/SymTracelessTensorValueTypes.jl b/src/TensorValues/SymTracelessTensorValueTypes.jl index c872641cd..8661d4161 100644 --- a/src/TensorValues/SymTracelessTensorValueTypes.jl +++ b/src/TensorValues/SymTracelessTensorValueTypes.jl @@ -36,6 +36,14 @@ end const QTensorValue = SymTracelessTensorValue +function promote_rule( + ::Type{<:SymTracelessTensorValue{D,Ta}}, + ::Type{<:SymTracelessTensorValue{D,Tb}}) where {D,Ta,Tb} + + T = promote_type(Ta,Tb) + SymTracelessTensorValue{D,T} +end + ############################################################### # Constructors (SymTracelessTensorValue) ############################################################### @@ -86,13 +94,15 @@ SymTracelessTensorValue{D,T1,L}(data::Number...) where {D,T1,L} = SymTracelessTe #From Square Matrices @generated function _flatten_upper_triangle_traceless(data::AbstractArray,::Val{D}) where D + check_e = :( @check ($D,$D) == size(data) ) str = "" for i in 1:D-1 for j in i:D str *= "data[$i,$j], " end end - Meta.parse("($str)") + ret_e = Meta.parse(" return ($str)") + Expr(:block, check_e, ret_e) end SymTracelessTensorValue(data::AbstractMatrix{T}) where {T} = ((D1,D2)=size(data); SymTracelessTensorValue{D1}(data)) @@ -114,73 +124,21 @@ SymTracelessTensorValue{D,T1,L}(data::AbstractMatrix{T2}) where {D,T1,T2,L} = Sy Meta.parse("SMatrix{D,D,T}(($str))") end -# Direct conversion -convert(::Type{<:SymTracelessTensorValue{D,T}}, arg::AbstractArray) where {D,T} = SymTracelessTensorValue{D,T}(arg) -convert(::Type{<:SymTracelessTensorValue{D,T}}, arg::Tuple) where {D,T} = SymTracelessTensorValue{D,T}(arg) - # Inverse conversion -convert(::Type{<:MMatrix{D,D,T}}, arg::SymTracelessTensorValue) where {D,T} = MMatrix{D,D,T}(_SymTracelessTensorValue_to_array(arg)) -convert(::Type{<:SMatrix{D,D,T}}, arg::SymTracelessTensorValue) where {D,T} = _SymTracelessTensorValue_to_array(arg) -convert(::Type{<:NTuple{L,T}}, arg::SymTracelessTensorValue) where {L,T} = NTuple{L,T}(Tuple(arg)) +convert(::Type{<:MArray{Tuple{D,D},T}}, arg::SymTracelessTensorValue) where {D,T} = MMatrix{D,D,T}(_SymTracelessTensorValue_to_array(arg)) +convert(::Type{<:SArray{Tuple{D,D},T}}, arg::SymTracelessTensorValue) where {D,T} = _SymTracelessTensorValue_to_array(arg) # Internal conversion -convert(::Type{<:SymTracelessTensorValue{D,T}}, arg::SymTracelessTensorValue{D}) where {D,T} = SymTracelessTensorValue{D,T}(Tuple(arg)) -convert(::Type{<:SymTracelessTensorValue{D,T}}, arg::SymTracelessTensorValue{D,T}) where {D,T} = arg +convert(::Type{<:SymTracelessTensorValue{D}}, arg::SymTracelessTensorValue{D}) where {D} = SymTracelessTensorValue{D}(Tuple(arg)[1:end-1]) +convert(::Type{<:SymTracelessTensorValue{D,T}}, arg::SymTracelessTensorValue{D}) where {D,T} = SymTracelessTensorValue{D,T}(Tuple(arg)[1:end-1]) ############################################################### # Other constructors and conversions (SymTracelessTensorValue) ############################################################### -zero(::Type{<:SymTracelessTensorValue{0,T}}) where {T} = SymTracelessTensorValue{0,T}() -@generated function zero(::Type{<:SymTracelessTensorValue{D,T}}) where {D,T} - L=D*(D+1)÷2-1 - quote - SymTracelessTensorValue{D,T}(tfill(zero(T),Val{$L}())) - end -end - -zero(::Type{<:SymTracelessTensorValue{D,T,L}}) where {D,T,L} = SymTracelessTensorValue{D,T}(tfill(zero(T),Val{L-1}())) -zero(::SymTracelessTensorValue{D,T,L}) where {D,T,L} = zero(SymTracelessTensorValue{D,T,L}) - -rand(::AbstractRNG, ::Random.SamplerType{<:SymTracelessTensorValue{0,T}}) where {T} = SymTracelessTensorValue{0,T}() -@generated function rand(rng::AbstractRNG, - ::Random.SamplerType{<:SymTracelessTensorValue{D,T}}) where {D,T} - L=D*(D+1)÷2 - quote - rand(rng, SymTracelessTensorValue{D,T,$L}) - end -end -rand(rng::AbstractRNG,::Random.SamplerType{<:SymTracelessTensorValue{D,T,L}}) where {D,T,L} = - SymTracelessTensorValue{D,T}(Tuple(rand(rng, SVector{L-1,T}))) - -Mutable(::Type{<:SymTracelessTensorValue{D,T}}) where {D,T} = MMatrix{D,D,T} -Mutable(::SymTracelessTensorValue{D,T}) where {D,T} = Mutable(SymTracelessTensorValue{D,T}) -mutable(a::SymTracelessTensorValue{D}) where D = MMatrix{D,D}(Tuple(get_array(a))) - -change_eltype(::Type{SymTracelessTensorValue{D,T1}},::Type{T2}) where {D,T1,T2} = SymTracelessTensorValue{D,T2} +change_eltype(::Type{<:SymTracelessTensorValue{D}},::Type{T2}) where {D,T2} = SymTracelessTensorValue{D,T2} change_eltype(::Type{SymTracelessTensorValue{D,T1,L}},::Type{T2}) where {D,T1,T2,L} = SymTracelessTensorValue{D,T2,L} -change_eltype(::SymTracelessTensorValue{D,T1,L},::Type{T2}) where {D,T1,T2,L} = change_eltype(SymTracelessTensorValue{D,T1,L},T2) - -get_array(arg::SymTracelessTensorValue{D,T,L}) where {D,T,L} = convert(SMatrix{D,D,T}, arg) - -############################################################### -# Introspection (SymTracelessTensorValue) -############################################################### - -eltype(::Type{<:SymTracelessTensorValue{D,T}}) where {D,T} = T -eltype(::SymTracelessTensorValue{D,T}) where {D,T} = eltype(SymTracelessTensorValue{D,T}) - -size(::Type{<:SymTracelessTensorValue{D}}) where {D} = (D,D) -size(::SymTracelessTensorValue{D}) where {D} = size(SymTracelessTensorValue{D}) -length(::Type{<:SymTracelessTensorValue{D}}) where {D} = D*D -length(::SymTracelessTensorValue{D}) where {D} = length(SymTracelessTensorValue{D}) - -num_components(::Type{<:SymTracelessTensorValue}) = @unreachable "The dimension is needed to count components" -num_components(::Type{<:SymTracelessTensorValue{D}}) where {D} = length(SymTracelessTensorValue{D}) -num_components(::SymTracelessTensorValue{D}) where {D} = num_components(SymTracelessTensorValue{D}) - -num_indep_components(::Type{<:SymTracelessTensorValue}) = num_components(SymTracelessTensorValue) -num_indep_components(::Type{SymTracelessTensorValue{0}}) = 0 +num_indep_components(::Type{<:SymTracelessTensorValue{0}}) = 0 num_indep_components(::Type{<:SymTracelessTensorValue{D}}) where {D} = D*(D+1)÷2-1 -num_indep_components(::SymTracelessTensorValue{D}) where {D} = num_indep_components(SymTracelessTensorValue{D}) + diff --git a/src/TensorValues/TensorValueTypes.jl b/src/TensorValues/TensorValueTypes.jl index a63ae03e0..a92b852fd 100644 --- a/src/TensorValues/TensorValueTypes.jl +++ b/src/TensorValues/TensorValueTypes.jl @@ -17,6 +17,11 @@ struct TensorValue{D1,D2,T,L} <: MultiValue{Tuple{D1,D2},T,2,L} end end +function promote_rule(::Type{<:TensorValue{D1,D2,Ta}}, ::Type{<:TensorValue{D1,D2,Tb}}) where {D1,D2,Ta,Tb} + T = promote_type(Ta,Tb) + TensorValue{D1,D2,T} +end + ############################################################### # Constructors ############################################################### @@ -73,14 +78,9 @@ TensorValue{D1,D2,T1,L}(data::AbstractMatrix{T2}) where {D1,D2,T1,T2,L} = Tensor # Conversions (TensorValue) ############################################################### -# Direct conversion -convert(::Type{<:TensorValue{D1,D2,T}}, arg::AbstractArray) where {D1,D2,T} = TensorValue{D1,D2,T}(arg) -convert(::Type{<:TensorValue{D1,D2,T}}, arg::Tuple) where {D1,D2,T} = TensorValue{D1,D2,T}(arg) - # Inverse conversion -convert(::Type{<:SMatrix{D1,D2,T}}, arg::TensorValue) where {D1,D2,T} = SMatrix{D1,D2,T}(Tuple(arg)) -convert(::Type{<:MMatrix{D1,D2,T}}, arg::TensorValue) where {D1,D2,T} = MMatrix{D1,D2,T}(Tuple(arg)) -convert(::Type{<:NTuple{L,T1}}, arg::TensorValue) where {L,T1} = NTuple{L,T1}(Tuple(arg)) +convert(::Type{<:SArray{Tuple{D1,D2},T}}, arg::TensorValue{D1,D2}) where {D1,D2,T} = SMatrix{D1,D2,T}(Tuple(arg)) +convert(::Type{<:MArray{Tuple{D1,D2},T}}, arg::TensorValue{D1,D2}) where {D1,D2,T} = MMatrix{D1,D2,T}(Tuple(arg)) # Internal conversion convert(::Type{<:TensorValue{D1,D2,T}}, arg::TensorValue{D1,D2}) where {D1,D2,T} = TensorValue{D1,D2,T}(Tuple(arg)) @@ -93,36 +93,13 @@ MultiValue(a::StaticMatrix{D1,D2,T}) where {D1,D2,T} = convert(TensorValue{D1,D2 # Other constructors and conversions (TensorValue) ############################################################### -zero(::Type{<:TensorValue{D1,D2,T}}) where {D1,D2,T} = TensorValue{D1,D2,T}(tfill(zero(T),Val{D1*D2}())) -zero(::TensorValue{D1,D2,T}) where {D1,D2,T} = zero(TensorValue{D1,D2,T}) - @generated function one(::Type{<:TensorValue{D1,D2,T}}) where {D1,D2,T} str = join(["$i==$j ? one(T) : zero(T), " for i in 1:D1 for j in 1:D2]) Meta.parse("TensorValue{D1,D2,T}(($str))") end -one(::TensorValue{D1,D2,T}) where {D1,D2,T} = one(TensorValue{D1,D2,T}) - -@generated function rand(rng::AbstractRNG, - ::Random.SamplerType{<:TensorValue{D1,D2,T}}) where {D1,D2,T} - L=D1*D2 - quote - rand(rng, TensorValue{D1,D2,T,$L}) - end -end -function rand(rng::AbstractRNG, - ::Random.SamplerType{<:TensorValue{D1,D2,T,L}}) where {D1,D2,T,L} - return TensorValue{D1,D2,T,L}(Tuple(rand(rng, SVector{L,T}))) -end -Mutable(::Type{<:TensorValue{D1,D2,T}}) where {D1,D2,T} = MMatrix{D1,D2,T} -Mutable(::TensorValue{D1,D2,T}) where {D1,D2,T} = Mutable(TensorValue{D1,D2,T}) -mutable(a::TensorValue{D1,D2}) where {D1,D2} = MMatrix{D1,D2}(a.data) - -change_eltype(::Type{TensorValue{D1,D2,T1}},::Type{T2}) where {D1,D2,T1,T2} = TensorValue{D1,D2,T2} +change_eltype(::Type{<:TensorValue{D1,D2}},::Type{T2}) where {D1,D2,T2} = TensorValue{D1,D2,T2} change_eltype(::Type{TensorValue{D1,D2,T1,L}},::Type{T2}) where {D1,D2,T1,T2,L} = TensorValue{D1,D2,T2,L} -change_eltype(::TensorValue{D1,D2,T1,L},::Type{T2}) where {D1,D2,T1,T2,L} = change_eltype(TensorValue{D1,D2,T1,L},T2) - -get_array(arg::TensorValue{D1,D2,T}) where {D1,D2,T} = convert(SMatrix{D1,D2,T},arg) """ diagonal_tensor(v::VectorValue{D,T}) -> ::TensorValue{D,D,T} @@ -173,26 +150,6 @@ end tensor_from_rows(rows::VectorValue...) = tensor_from_rows(rows) -############################################################### -# Introspection (TensorValue) -############################################################### - -eltype(::Type{<:TensorValue{D1,D2,T}}) where {D1,D2,T} = T -eltype(::TensorValue{D1,D2,T}) where {D1,D2,T} = eltype(TensorValue{D1,D2,T}) - -size(::Type{<:TensorValue{D}}) where {D} = (D,D) -size(::Type{<:TensorValue{D1,D2}}) where {D1,D2} = (D1,D2) -size(::TensorValue{D1,D2}) where {D1,D2} = size(TensorValue{D1,D2}) - -length(::Type{<:TensorValue{D}}) where {D} = length(TensorValue{D,D}) -length(::Type{<:TensorValue{D1,D2}}) where {D1,D2} = D1*D2 -length(::TensorValue{D1,D2}) where {D1,D2} = length(TensorValue{D1,D2}) - -num_components(::Type{<:TensorValue}) = @unreachable "All two size dimensions are needed to count components" -num_components(::Type{<:TensorValue{D,D}}) where {D} = length(TensorValue{D,D}) -num_components(::Type{<:TensorValue{D1,D2}}) where {D1,D2} = length(TensorValue{D1,D2}) -num_components(::TensorValue{D1,D2}) where {D1,D2} = num_components(TensorValue{D1,D2}) - ############################################################### # VTK export (TensorValue) ############################################################### diff --git a/src/TensorValues/TensorValues.jl b/src/TensorValues/TensorValues.jl index 10bec4a04..c8622d4d9 100644 --- a/src/TensorValues/TensorValues.jl +++ b/src/TensorValues/TensorValues.jl @@ -1,38 +1,37 @@ """ -Immutable tensor types for Gridap. The currently implemented tensor types are -- 1st order [`VectorValue`](@ref), -- 2nd order [`TensorValue`](@ref), -- 2nd order and symmetric [`SymTensorValue`](@ref), -- 2nd order, symmetric and traceless [`SymTracelessTensorValue`](@ref), -- 3rd order [`ThirdOrderTensorValue`](@ref), -- 4th order and symmetric [`SymFourthOrderTensorValue`](@ref). - -Example usage: +This module provide tensor types of supertype [`MultiValue`](@ref) that have +very similar design and API to `S/MArray` from StaticArrays.jl, but with +efficient storage for tensors with dependent components (e.g. symmetry). The +main feature of this module is that `MultiValue` doesn't subtype `AbstractArray`, +but `Number`! + +This allows one to work with them as if they were scalar values in broadcasted +operations on arrays of `VectorValue` objects (also for `TensorValue` or any +`<:MultiValue` objects). For instance, one can perform the following manipulations: ```julia -# create a 2D vector from components -v = VectorValue(12,31) - # Assign a VectorValue to all the entries of an Array of VectorValues A = zeros(VectorValue{2,Int}, (4,5)) +v = VectorValue(12,31) A .= v # This is possible since VectorValue <: Number -using StaticArrays -# create 2x2 tensor from component tuple -t = TensorValue( (1, 2, 3, 4) ) -# conversion to StaticArrays type -ts= convert(SMatrix{2,2,Int}, t) -@show ts -# 2×2 SMatrix{2, 2, Int64, 4} with indices SOneTo(2)×SOneTo(2): -# 1 3 -# 2 4 -t2[1,2] == t[1,2] == 3 # true - -# conversion from Array or StaticArray types, symmetric tensor types only store required components -SymTensorValue( [1 2; 3 4] ) # SymTensorValue{2, Int64, 3}(1, 2, 4) -SymTensorValue( SMatrix{2}(1,2,3,4) ) # SymTensorValue{2, Int64, 3}(1, 3, 4) +# Broadcasting of tensor operations in arrays of TensorValues +t = TensorValue(13,41,53,17) # creates a 2x2 TensorValue +g = TensorValue(32,41,3,14) # creates another 2x2 TensorValue +B = fill(t,(1,5)) +C = inner.(g,B) # inner product of g against all TensorValues in the array B +@show C +# C = [2494 2494 2494 2494 2494] ``` -See the official documentation for more details. +$(public_names_in_md(@__MODULE__; change_link=Dict( + :QTensorValue => "SymTracelessTensorValue", + :× => "cross", + :⊗ => "outer", + :⊙ => "inner", + :⋅ => "dot", + :⋅¹ => "dot", + :⋅² => "double_contraction", +))) """ module TensorValues @@ -53,6 +52,7 @@ export TensorValue export AbstractSymTensorValue export SymTensorValue export SymTracelessTensorValue +export SkewSymTensorValue export QTensorValue export SymFourthOrderTensorValue export ThirdOrderTensorValue @@ -62,7 +62,6 @@ export mutable export Mutable export symmetric_part export skew_symmetric_part -export n_components export num_components export num_indep_components export change_eltype @@ -72,13 +71,16 @@ export ⊗ export ⋅¹ export ⋅² export double_contraction -export data_index +export congruent_prod export indep_comp_getindex export indep_components_names +export component_basis +export representatives_of_componentbasis_dual import Base: show +import Base: promote_rule import Base: zero, one -import Base: +, -, *, /, \, ==, ≈, isless +import Base: +, -, *, /, \, ==, ≈, isless, <= import Base: conj, real, imag import Base: sum, maximum, minimum import Base: getindex, iterate, eachindex, lastindex @@ -106,6 +108,8 @@ include("SymTensorValueTypes.jl") include("SymTracelessTensorValueTypes.jl") +include("SkewSymTensorValueTypes.jl") + include("SymFourthOrderTensorValueTypes.jl") include("ThirdOrderTensorValueTypes.jl") diff --git a/src/TensorValues/ThirdOrderTensorValueTypes.jl b/src/TensorValues/ThirdOrderTensorValueTypes.jl index e857b3ce7..8defd15c5 100644 --- a/src/TensorValues/ThirdOrderTensorValueTypes.jl +++ b/src/TensorValues/ThirdOrderTensorValueTypes.jl @@ -14,6 +14,14 @@ struct ThirdOrderTensorValue{D1,D2,D3,T,L} <: MultiValue{Tuple{D1,D2,D3},T,3,L} end end +function promote_rule( + ::Type{<:ThirdOrderTensorValue{D1,D2,D3,Ta}}, + ::Type{<:ThirdOrderTensorValue{D1,D2,D3,Tb}}) where {D1,D2,D3,Ta,Tb} + + T = promote_type(Ta,Tb) + ThirdOrderTensorValue{D1,D2,D3,T} +end + # Empty ThirdOrderTensorValue constructor ThirdOrderTensorValue() = ThirdOrderTensorValue{0,0,0,Int}(NTuple{0,Int}()) @@ -25,7 +33,10 @@ ThirdOrderTensorValue{0,0,0}(data::NTuple{0}) = ThirdOrderTensorValue{0,0,0,Int} # ThirdOrderTensorValue single NTuple argument constructor @generated function ThirdOrderTensorValue(data::NTuple{L,T}) where {L,T} - D=Int(cbrt(L)) + msg = "Invalid number of scalar arguments in ThirdOrderTensorValue constructor, expecting a cube number, got L=$L" + V = cbrt(L) + @assert floor(Int,V) == ceil(Int,V) msg + D=Int(V) quote ThirdOrderTensorValue{$D,$D,$D,T}(data) end @@ -63,14 +74,9 @@ ThirdOrderTensorValue{D1,D2,D3,T1,L}(data::AbstractArray{T2,3}) where {D1,D2,D3, # Conversions (ThirdOrderTensorValue) ############################################################### -# Direct conversion -convert(::Type{<:ThirdOrderTensorValue{D1,D2,D3,T}}, arg::AbstractArray) where {D1,D2,D3,T} = ThirdOrderTensorValue{D1,D2,D3,T}(arg) -convert(::Type{<:ThirdOrderTensorValue{D1,D2,D3,T}}, arg::Tuple) where {D1,D2,D3,T} = ThirdOrderTensorValue{D1,D2,D3,T}(arg) - # Inverse conversion convert(::Type{<:SArray{Tuple{D1,D2,D3},T}}, arg::ThirdOrderTensorValue) where {D1,D2,D3,T} = SArray{Tuple{D1,D2,D3},T}(Tuple(arg)) convert(::Type{<:MArray{Tuple{D1,D2,D3},T}}, arg::ThirdOrderTensorValue) where {D1,D2,D3,T} = MArray{Tuple{D1,D2,D3},T}(Tuple(arg)) -convert(::Type{<:NTuple{L,T1}}, arg::ThirdOrderTensorValue) where {L,T1} = NTuple{L,T1}(Tuple(arg)) # Internal conversion convert(::Type{<:ThirdOrderTensorValue{D1,D2,D3,T}}, arg::ThirdOrderTensorValue{D1,D2,D3}) where {D1,D2,D3,T} = ThirdOrderTensorValue{D1,D2,D3,T}(Tuple(arg)) @@ -80,48 +86,8 @@ convert(::Type{<:ThirdOrderTensorValue{D1,D2,D3,T}}, arg::ThirdOrderTensorValue{ MultiValue(a::StaticArray{Tuple{D1,D2,D3},T}) where {D1,D2,D3,T} = convert(ThirdOrderTensorValue{D1,D2,D3,T}, a) # other - -change_eltype(::Type{ThirdOrderTensorValue{D1,D2,D3,T1}},::Type{T2}) where {D1,D2,D3,T1,T2} = ThirdOrderTensorValue{D1,D2,D3,T2} +change_eltype(::Type{<:ThirdOrderTensorValue{D1,D2,D3}},::Type{T2}) where {D1,D2,D3,T2} = ThirdOrderTensorValue{D1,D2,D3,T2} change_eltype(::Type{ThirdOrderTensorValue{D1,D2,D3,T1,L}},::Type{T2}) where {D1,D2,D3,T1,T2,L} = ThirdOrderTensorValue{D1,D2,D3,T2,L} -change_eltype(::T,::Type{T2}) where {T<:ThirdOrderTensorValue,T2} = change_eltype(T,T2) - -get_array(arg::ThirdOrderTensorValue{D1,D2,D3,T}) where {D1,D2,D3,T} = convert(SArray{Tuple{D1,D2,D3},T},arg) - -zero(::Type{<:ThirdOrderTensorValue{D1,D2,D3,T}}) where {D1,D2,D3,T} = ThirdOrderTensorValue{D1,D2,D3,T}(tfill(zero(T),Val{D1*D2*D3}())) -zero(::ThirdOrderTensorValue{D1,D2,D3,T}) where {D1,D2,D3,T} = zero(ThirdOrderTensorValue{D1,D2,D3,T}) - -@generated function rand(rng::AbstractRNG, - ::Random.SamplerType{<:ThirdOrderTensorValue{D1,D2,D3,T}}) where {D1,D2,D3,T} - L=D1*D2*D3 - quote - rand(rng, ThirdOrderTensorValue{D1,D2,D3,T,$L}) - end -end -function rand(rng::AbstractRNG, - ::Random.SamplerType{<:ThirdOrderTensorValue{D1,D2,D3,T,L}}) where {D1,D2,D3,T,L} - return ThirdOrderTensorValue{D1,D2,D3,T,L}(Tuple(rand(rng, SVector{L,T}))) -end - -Mutable(::Type{<:ThirdOrderTensorValue{D1,D2,D3,T}}) where {D1,D2,D3,T} = MArray{Tuple{D1,D2,D3},T} -Mutable(::ThirdOrderTensorValue{D1,D2,D3,T}) where {D1,D2,D3,T} = Mutable(ThirdOrderTensorValue{D1,D2,D3,T}) -mutable(a::ThirdOrderTensorValue{D1,D2,D3}) where {D1,D2,D3} = MArray{Tuple{D1,D2,D3}}(a.data) - -############################################################### -# Introspection (ThirdOrderTensorValue) -############################################################### - -eltype(::Type{<:ThirdOrderTensorValue{D1,D2,D3,T}}) where {D1,D2,D3,T} = T -eltype(::ThirdOrderTensorValue{D1,D2,D3,T}) where {D1,D2,D3,T} = eltype(ThirdOrderTensorValue{D1,D2,D3,T}) - -size(::Type{<:ThirdOrderTensorValue{D1,D2,D3}}) where {D1,D2,D3} = (D1,D2,D3) -size(::ThirdOrderTensorValue{D1,D2,D3}) where {D1,D2,D3} = size(ThirdOrderTensorValue{D1,D2,D3}) - -length(::Type{<:ThirdOrderTensorValue{D1,D2,D3}}) where {D1,D2,D3} = D1*D2*D3 -length(::ThirdOrderTensorValue{D1,D2,D3}) where {D1,D2,D3} = length(ThirdOrderTensorValue{D1,D2,D3}) - -num_components(::Type{<:ThirdOrderTensorValue}) = @unreachable "All three size dimensions are needed to count components" -num_components(::Type{<:ThirdOrderTensorValue{D1,D2,D3}}) where {D1,D2,D3} = length(ThirdOrderTensorValue{D1,D2,D3}) -num_components(::ThirdOrderTensorValue{D1,D2,D3}) where {D1,D2,D3} = num_components(ThirdOrderTensorValue{D1,D2,D3}) ############################################################### # VTK export (ThirdOrderTensorValue) diff --git a/src/TensorValues/VectorValueTypes.jl b/src/TensorValues/VectorValueTypes.jl index 485ed9c54..09ac6e9bc 100644 --- a/src/TensorValues/VectorValueTypes.jl +++ b/src/TensorValues/VectorValueTypes.jl @@ -14,6 +14,10 @@ struct VectorValue{D,T} <: MultiValue{Tuple{D},T,1,D} end end +function promote_rule(::Type{VectorValue{D,Ta}}, ::Type{VectorValue{D,Tb}}) where {D,Ta,Tb} + VectorValue{D,promote_type(Ta,Tb)} +end + ############################################################### # Constructors (VectorValue) ############################################################### @@ -60,60 +64,19 @@ VectorValue{D,T1}(data::AbstractArray{T2}) where {D,T1,T2} = VectorValue{D,T1}(N # Conversions (VectorValue) ############################################################### -# Direct conversion -convert(::Type{<:VectorValue{D,T}}, arg:: AbstractArray) where {D,T} = VectorValue{D,T}(NTuple{D,T}(arg)) -convert(::Type{<:VectorValue{D,T}}, arg:: Tuple) where {D,T} = VectorValue{D,T}(arg) - # Inverse conversion -convert(::Type{<:SVector{D,T}}, arg::VectorValue{D}) where {D,T} = SVector{D,T}(Tuple(arg)) -convert(::Type{<:MVector{D,T}}, arg::VectorValue{D}) where {D,T} = MVector{D,T}(Tuple(arg)) -convert(::Type{<:NTuple{D,T}}, arg::VectorValue{D}) where {D,T} = NTuple{D,T}(Tuple(arg)) +convert(::Type{<:SArray{Tuple{D},T}}, arg::VectorValue{D}) where {D,T} = SVector{D,T}(Tuple(arg)) +convert(::Type{<:MArray{Tuple{D},T}}, arg::VectorValue{D}) where {D,T} = MVector{D,T}(Tuple(arg)) # Internal conversion convert(::Type{<:VectorValue{D,T}}, arg::VectorValue{D}) where {D,T} = VectorValue{D,T}(Tuple(arg)) convert(::Type{<:VectorValue{D,T}}, arg::VectorValue{D,T}) where {D,T} = arg +change_eltype(::Type{<:VectorValue{D}},::Type{T}) where {D,T} = VectorValue{D,T} + # Construction from SArray or MArray MultiValue(a::StaticVector{D,T}) where {D,T} = convert(VectorValue{D,T}, a) -############################################################### -# Other constructors and conversions (VectorValue) -############################################################### - -zero(::Type{<:VectorValue{D,T}}) where {D,T} = VectorValue{D,T}(tfill(zero(T),Val{D}())) -zero(::VectorValue{D,T}) where {D,T} = zero(VectorValue{D,T}) - -function rand(rng::AbstractRNG, ::Random.SamplerType{VectorValue{D,T}}) where {D,T} - return VectorValue{D,T}(Tuple(rand(rng, SVector{D,T}))) -end - -Mutable(::Type{VectorValue{D,T}}) where {D,T} = MVector{D,T} -Mutable(::VectorValue{D,T}) where {D,T} = Mutable(VectorValue{D,T}) -mutable(a::VectorValue) = MVector(a.data) - -change_eltype(::Type{VectorValue{D}},::Type{T}) where {D,T} = VectorValue{D,T} -change_eltype(::Type{VectorValue{D,T1}},::Type{T2}) where {D,T1,T2} = VectorValue{D,T2} -change_eltype(::VectorValue{D,T1},::Type{T2}) where {D,T1,T2} = change_eltype(VectorValue{D,T1},T2) - -get_array(arg::VectorValue{D,T}) where {D,T} = convert(SVector{D,T}, arg) - -############################################################### -# Introspection (VectorValue) -############################################################### - -eltype(::Type{<:VectorValue{D,T}}) where {D,T} = T -eltype(arg::VectorValue{D,T}) where {D,T} = eltype(VectorValue{D,T}) - -size(::Type{<:VectorValue{D}}) where {D} = (D,) -size(::VectorValue{D}) where {D} = size(VectorValue{D}) - -length(::Type{<:VectorValue{D}}) where {D} = D -length(::VectorValue{D}) where {D} = length(VectorValue{D}) - -num_components(::Type{<:VectorValue}) = @unreachable "The dimension is needed to count components" -num_components(::Type{<:VectorValue{D}}) where {D} = length(VectorValue{D}) -num_components(::VectorValue{D}) where {D} = num_components(VectorValue{D}) - ############################################################### # VTK export (VectorValue) ############################################################### diff --git a/src/Visualization/Visualization.jl b/src/Visualization/Visualization.jl index 25be3ef8f..12201ee5d 100644 --- a/src/Visualization/Visualization.jl +++ b/src/Visualization/Visualization.jl @@ -1,7 +1,6 @@ """ -The exported names are -$(EXPORTS) +$(public_names_in_md(@__MODULE__)) """ module Visualization diff --git a/src/Visualization/VisualizationData.jl b/src/Visualization/VisualizationData.jl index bc14bd869..c5d63553b 100644 --- a/src/Visualization/VisualizationData.jl +++ b/src/Visualization/VisualizationData.jl @@ -1,9 +1,13 @@ - +""" + struct VisualizationData + VisualizationData(grid::Grid, filebase::AbstractString; celldata=Dict(), nodaldata=Dict()) +""" struct VisualizationData grid::Grid filebase::AbstractString celldata nodaldata + function VisualizationData(grid::Grid,filebase::AbstractString;celldata=Dict(),nodaldata=Dict()) new(grid,filebase,celldata,nodaldata) end diff --git a/test/AdaptivityTests/AdaptiveMeshRefinementTests.jl b/test/AdaptivityTests/AdaptiveMeshRefinementTests.jl index 690408b8f..be58b50fc 100644 --- a/test/AdaptivityTests/AdaptiveMeshRefinementTests.jl +++ b/test/AdaptivityTests/AdaptiveMeshRefinementTests.jl @@ -14,8 +14,8 @@ function test_dorfler_marking() m = DorflerMarking(θ;strategy) I = Adaptivity.mark(m,η) @test sum(η[I]) > θ * sum(η) - println("Strategy: $strategy, θ: $θ, n: $n") - println(" > N marked = $(length(I)), val marked = $(sum(η[I]) / sum(η))") + #println("Strategy: $strategy, θ: $θ, n: $n") + #println(" > N marked = $(length(I)), val marked = $(sum(η[I]) / sum(η))") end end end @@ -38,15 +38,15 @@ function amr_step(model,u_exact;order=1) reffe = ReferenceFE(lagrangian,Float64,order) V = TestFESpace(model,reffe;dirichlet_tags=["boundary"]) U = TrialFESpace(V,u_exact) - + Ω = Triangulation(model) Γ = Boundary(model) Λ = Skeleton(model) - + dΩ = Measure(Ω,4*order) dΓ = Measure(Γ,2*order) dΛ = Measure(Λ,2*order) - + hK = CellField(sqrt.(collect(get_array(∫(1)dΩ))),Ω) nΓ = get_normal_vector(Γ) @@ -57,14 +57,14 @@ function amr_step(model,u_exact;order=1) a(u,v) = ∫(∇(u)⋅∇(v))dΩ l(v) = ∫(f*v)dΩ ηh(u) = l2_norm(hK*(f + Δ(u)),dΩ) + l2_norm(hK*(∇(u) - ∇u)⋅nΓ,dΓ) + l2_norm(jump(hK*∇(u)⋅nΛ),dΛ) - + op = AffineFEOperator(a,l,U,V) uh = solve(op) η = estimate(ηh,uh) - + m = DorflerMarking(0.8) I = Adaptivity.mark(m,η) - + method = Adaptivity.NVBRefinement(model) fmodel = Adaptivity.get_model(refine(method,model;cells_to_refine=I)) @@ -97,7 +97,7 @@ function test_amr(nsteps,order) ) end - println("Error: $error, Error η: $(sum(η))") + #println("Error: $error, Error η: $(sum(η))") @test (i < 3) || (error < last_error) last_error = error model = fmodel @@ -109,4 +109,4 @@ end @testset "Dorfler marking" test_dorfler_marking() @testset "AMR - Poisson" test_amr(10,2) -end # module \ No newline at end of file +end # module diff --git a/test/AdaptivityTests/CartesianRefinementTests.jl b/test/AdaptivityTests/CartesianRefinementTests.jl index 1f91dffce..8c9b6675f 100644 --- a/test/AdaptivityTests/CartesianRefinementTests.jl +++ b/test/AdaptivityTests/CartesianRefinementTests.jl @@ -40,8 +40,6 @@ function test_grid_transfers(D,model,parent,order) U_f = TrialFESpace(V_f,sol) V_c = TestFESpace(parent,reffe;dirichlet_tags="boundary") U_c = TrialFESpace(V_c,sol) - V_c_fast = TestFESpace(parent,rrules,reffe;dirichlet_tags="boundary") - U_c_fast = TrialFESpace(V_c_fast,sol) # CellField: Coarse -> Fine cf_c_phy = CellField(sol,ctrian) @@ -80,14 +78,12 @@ function test_grid_transfers(D,model,parent,order) uh_c_inter = interpolate(uh_f,U_c) uh_c_inter2 = interpolate_everywhere(uh_f,U_c) uh_c_inter3 = interpolate_dirichlet(uh_f,U_c) - uh_c_inter4 = interpolate(uh_f,U_c_fast) @test l2_error(uh_f,uh_f_inter,dΩ_f) < 1.e-8 @test l2_error(uh_f,uh_f_inter2,dΩ_f) < 1.e-8 @test l2_error(uh_c,uh_c_inter,dΩ_c) < 1.e-8 @test l2_error(uh_c,uh_c_inter2,dΩ_c) < 1.e-8 - @test l2_error(uh_c,uh_c_inter4,dΩ_c) < 1.e-8 # Coarse FEFunction -> Fine FEFunction, by projection af(u,v) = ∫(v⋅u)*dΩ_f diff --git a/test/AdaptivityTests/EdgeBasedRefinementTests.jl b/test/AdaptivityTests/EdgeBasedRefinementTests.jl index d2b2bfd44..31d588b85 100644 --- a/test/AdaptivityTests/EdgeBasedRefinementTests.jl +++ b/test/AdaptivityTests/EdgeBasedRefinementTests.jl @@ -38,8 +38,6 @@ function test_grid_transfers(parent,model,order) U_f = TrialFESpace(V_f,sol) V_c = TestFESpace(parent,reffe;dirichlet_tags="boundary") U_c = TrialFESpace(V_c,sol) - V_c_fast = TestFESpace(parent,rrules,reffe;dirichlet_tags="boundary") - U_c_fast = TrialFESpace(V_c_fast,sol) # CellField: Coarse -> Fine cf_c_phy = CellField(sol,ctrian) @@ -78,14 +76,12 @@ function test_grid_transfers(parent,model,order) uh_c_inter = interpolate(uh_f,U_c) uh_c_inter2 = interpolate_everywhere(uh_f,U_c) uh_c_inter3 = interpolate_dirichlet(uh_f,U_c) - uh_c_inter4 = interpolate(uh_f,U_c_fast) @test l2_error(uh_f,uh_f_inter,dΩ_f) < 1.e-8 @test l2_error(uh_f,uh_f_inter2,dΩ_f) < 1.e-8 @test l2_error(uh_c,uh_c_inter,dΩ_c) < 1.e-8 @test l2_error(uh_c,uh_c_inter2,dΩ_c) < 1.e-8 - @test l2_error(uh_c,uh_c_inter4,dΩ_c) < 1.e-8 # Coarse FEFunction -> Fine FEFunction, by projection af(u,v) = ∫(v⋅u)*dΩ_f diff --git a/test/AdaptivityTests/runtests.jl b/test/AdaptivityTests/runtests.jl index 69203e4ce..f33220eba 100644 --- a/test/AdaptivityTests/runtests.jl +++ b/test/AdaptivityTests/runtests.jl @@ -9,7 +9,6 @@ using Test include("CartesianRefinementTests.jl") include("ComplexChangeDomainTests.jl") include("EdgeBasedRefinementTests.jl") - include("FineToCoarseFieldsTests.jl") include("RefinementRuleBoundaryTests.jl") include("MultifieldRefinementTests.jl") end diff --git a/test/ArraysTests/ArrayBlockTests.jl b/test/ArraysTests/ArrayBlockTests.jl index af785f962..15d364cae 100644 --- a/test/ArraysTests/ArrayBlockTests.jl +++ b/test/ArraysTests/ArrayBlockTests.jl @@ -8,8 +8,6 @@ using LinearAlgebra A = ArrayBlock(reshape([fill(1.0,2,3),fill(0.0,0,0),fill(2.0,2,5), fill(3.0,4,5)],(2,2)), Bool[1 1; 0 1]) B = ArrayBlock([fill(4.0,3),fill(5.0,5)], Bool[1,1]) -show(A) -display(A) @test A ≈ copy(A) C = 3.0*A diff --git a/test/ArraysTests/MapsTests.jl b/test/ArraysTests/MapsTests.jl index 2450344c1..880ea095c 100644 --- a/test/ArraysTests/MapsTests.jl +++ b/test/ArraysTests/MapsTests.jl @@ -62,7 +62,7 @@ c = zeros(VectorValue{3,Int},2) broadcast!(⋅,c,a,b) test_map(c,f,a,b) # broadcast(⋅,a,b) to be fixed ? -@test_throws "no method matching Int" broadcast(⋅,a,b) +@test_throws ErrorException broadcast(⋅,a,b) cache = return_cache(f,a,b) # @btime evaluate!($cache,$f,$a,$b) diff --git a/test/FESpacesTests/CellFieldsTests.jl b/test/FESpacesTests/CellFieldsTests.jl index 75c931605..0c966ea7d 100644 --- a/test/FESpacesTests/CellFieldsTests.jl +++ b/test/FESpacesTests/CellFieldsTests.jl @@ -234,7 +234,7 @@ end @test cf4(p) ≈ ∇∇(uh)(p) + ∇∇(qh)(p) cf5 = ∇∇(uh*qh) - @test get_array(cf5(p)) ≈ get_array(qh(p) * ∇∇(uh)(p) + uh(p) * ∇∇(qh)(p) + ∇(qh)(p) ⊗ ∇(uh)(p) + ∇(uh)(p) ⊗ ∇(qh)(p)) + @test get_array(cf5(p)) ≈ get_array(qh(p) * ∇∇(uh)(p) + uh(p) * ∇∇(qh)(p) + ∇(qh)(p) ⊗ ∇(uh)(p) + ∇(uh)(p) ⊗ ∇(qh)(p)) cf6 = Δ(uh*qh) @test cf6(p) ≈ tr(get_array(qh(p) * ∇∇(uh)(p) + uh(p) * ∇∇(qh)(p) + ∇(qh)(p) ⊗ ∇(uh)(p) + ∇(uh)(p) ⊗ ∇(qh)(p))) @@ -252,7 +252,7 @@ end @test prod(cf4(cp) .≈ ∇∇(uh)(cp) + ∇∇(qh)(cp)) - @test prod(cf5(cp) .≈ (qh * ∇∇(uh))(cp) + (uh * ∇∇(qh))(cp) + (∇(qh) ⊗ ∇(uh))(cp) + (∇(uh) ⊗ ∇(qh))(cp)) + @test prod(cf5(cp) .≈ (qh * ∇∇(uh))(cp) + (uh * ∇∇(qh))(cp) + (∇(qh) ⊗ ∇(uh))(cp) + (∇(uh) ⊗ ∇(qh))(cp)) @test prod(cf6(cp) .≈ map(i -> tr.(i), cf5(cp))) end diff --git a/test/FESpacesTests/CurlConformingFESpacesTests.jl b/test/FESpacesTests/CurlConformingFESpacesTests.jl index ae82cb099..8c307caf8 100644 --- a/test/FESpacesTests/CurlConformingFESpacesTests.jl +++ b/test/FESpacesTests/CurlConformingFESpacesTests.jl @@ -9,12 +9,12 @@ using Gridap.CellData using Gridap.Fields using Gridap.ReferenceFEs -domain =(0,1,0,1) +domain = (0,1,0,1) partition = (2,2) model = CartesianDiscreteModel(domain,partition) -order = 1 +order = 2 u((x,y)) = 2*VectorValue(2,3) -reffe = ReferenceFE(nedelec,order) +reffe = ReferenceFE(QUAD,nedelec,order) V = TestFESpace(model,reffe,dirichlet_tags = "boundary") test_single_field_fe_space(V) U = TrialFESpace(V,u) @@ -27,12 +27,12 @@ el2 = sqrt(sum( ∫( e⋅e )*dΩ )) #using Gridap.Visualization #writevtk(Ω,"nedel",nsubcells=10,cellfields=["err"=>e,"u"=>u,"uh"=>uh]) -domain =(0,1,0,1) +domain = (0,1,0,1) partition = (3,3) model = CartesianDiscreteModel(domain,partition) |> simplexify order = 0 u((x,y)) = 2*VectorValue(-y,x) -reffe = ReferenceFE(nedelec,order) +reffe = ReferenceFE(TRI,nedelec,order) V = TestFESpace(model,reffe,dirichlet_tags = "boundary") test_single_field_fe_space(V) U = TrialFESpace(V,u) @@ -74,7 +74,7 @@ model = CartesianDiscreteModel(domain,partition) |> simplexify dΩ = Measure(Ω,order) u((x,y,z)) = 2*VectorValue(-y,x,0.) - VectorValue(0.,-z,y) order = 0 -reffe = ReferenceFE(nedelec,order) +reffe = ReferenceFE(TET,nedelec,order) V = TestFESpace(model,reffe,dirichlet_tags = "boundary") test_single_field_fe_space(V) U = TrialFESpace(V,u) @@ -101,7 +101,7 @@ e = u - uh el2 = sqrt(sum( ∫( e⋅e )*dΩ )) @test el2 < 1.0e-10 -domain =(0,1,0,1,0,1) +domain = (0,1,0,1,0,1) partition = (3,3,3) model = CartesianDiscreteModel(domain,partition) @@ -111,8 +111,7 @@ u(x) = VectorValue(x[1]*x[1],x[1]*x[1]*x[1],0.0) #u(x) = VectorValue(2,3,5) # u(x) = x -reffe = ReferenceFE(nedelec,order) - +reffe = ReferenceFE(HEX,nedelec,order) V = TestFESpace(model,reffe,dirichlet_tags = [21,22]) # dirichlet_tags = "boundary") @@ -128,7 +127,7 @@ e = u - uh dΩ = Measure(Ω,order) el2 = sqrt(sum( ∫( e⋅e )*dΩ )) -@test el2 < 1.0e-10 +@test el2 < 2.0e-10 domain = (0,1,0,1,0,1) cells = (2,2,2) @@ -141,7 +140,6 @@ bgface_to_mask = get_face_mask(labels,"boundary",2) Dc2Dp3model = DiscreteModelPortion(DiscreteModel(Polytope{2},model),Γface_to_bgface) order = 0 -degree = 1 reffe_nd = ReferenceFE(nedelec,Float64,order) V = TestFESpace(Dc2Dp3model, reffe_nd ; conformity=:HCurl) diff --git a/test/FESpacesTests/DivConformingFESpacesTests.jl b/test/FESpacesTests/DivConformingFESpacesTests.jl index 8bda23e06..46e256141 100644 --- a/test/FESpacesTests/DivConformingFESpacesTests.jl +++ b/test/FESpacesTests/DivConformingFESpacesTests.jl @@ -2,6 +2,7 @@ module DivConformingFESpacesTests using Test using Gridap.Geometry +using Gridap.Arrays using Gridap.ReferenceFEs using Gridap.FESpaces using Gridap.CellData @@ -10,19 +11,19 @@ using Gridap.Fields using Gridap.Io function test_div_v_q_equiv(U,V,P,Q,Ω) - v=get_fe_basis(V) - u=get_trial_fe_basis(U) + v = get_fe_basis(V) + u = get_trial_fe_basis(U) - q=get_fe_basis(Q) - p=get_trial_fe_basis(P) + q = get_fe_basis(Q) + p = get_trial_fe_basis(P) - dΩ=Measure(Ω,1) - dΩᵣ=Measure(Ω,1,integration_domain_style=ReferenceDomain()) + dΩ = Measure(Ω,1) + dΩᵣ = Measure(Ω,1,integration_domain_style = ReferenceDomain()) - a1(p,v)=∫(divergence(v)*p)dΩ - a2(p,v)=∫(DIV(v)*p)dΩᵣ + a1(p,v) = ∫(divergence(v)*p)dΩ + a2(p,v) = ∫(DIV(v)*p)dΩᵣ - tol=1.0e-12 + tol = 1.0e-12 assem = SparseMatrixAssembler(P,V) data = collect_cell_matrix(P,V,a1(p,v)) A1 = assemble_matrix(assem,data) @@ -30,8 +31,8 @@ function test_div_v_q_equiv(U,V,P,Q,Ω) A2 = assemble_matrix(assem,data) @test norm(A1-A2) < tol - a3(u,q)=∫(q*divergence(u))dΩ - a4(u,q)=∫(q*DIV(u))dΩᵣ + a3(u,q) = ∫(q*divergence(u))dΩ + a4(u,q) = ∫(q*DIV(u))dΩᵣ assem = SparseMatrixAssembler(U,Q) data = collect_cell_matrix(U,Q,a3(u,q)) A3 = assemble_matrix(assem,data) @@ -39,36 +40,33 @@ function test_div_v_q_equiv(U,V,P,Q,Ω) A4 = assemble_matrix(assem,data) @test norm(A3-A4) < tol - uh=FEFunction(U,rand(num_free_dofs(U))) - l1(q)=∫(q*divergence(uh))dΩ - l2(q)=∫(q*DIV(uh))dΩᵣ - v1=assemble_vector(l1,Q) - v2=assemble_vector(l2,Q) + uh = FEFunction(U,rand(num_free_dofs(U))) + l1(q) = ∫(q*divergence(uh))dΩ + l2(q) = ∫(q*DIV(uh))dΩᵣ + v1 = assemble_vector(l1,Q) + v2 = assemble_vector(l2,Q) @test norm(v1-v2) < tol end @testset "Test Raviart-Thomas" begin - domain =(0,1,0,1) + domain = (0,1,0,1) partition = (3,3) model = CartesianDiscreteModel(domain,partition) order = 1 - u(x) = x - reffe = ReferenceFE(raviart_thomas,order) - + reffe = ReferenceFE(QUAD,raviart_thomas,order) V = TestFESpace(model,reffe,dirichlet_tags = [1,6]) test_single_field_fe_space(V) U = TrialFESpace(V,u) - reffe = ReferenceFE(lagrangian,Float64,order) - Q = TestFESpace(model,reffe,conformity=:L2) + reffe_p = ReferenceFE(lagrangian,Float64,order) + Q = TestFESpace(model,reffe_p,conformity=:L2) P = TrialFESpace(Q) uh = interpolate(u,U) - e = u - uh Ω = Triangulation(model) @@ -82,29 +80,25 @@ end #using Gridap.Visualization # #writevtk(Ω,"trian",nsubcells=10,cellfields=["uh"=>uh]) - order = 1 - reffe = ReferenceFE(TET,raviart_thomas,order) + reffe = ReferenceFE(TRI,raviart_thomas,order) - domain =(0,1,0,1,0,1) - partition = (3,3,3) + domain = (0,1,0,2) + partition = (2,2) model = simplexify(CartesianDiscreteModel(domain,partition)) - labels = get_face_labeling(model) - dir_tags = Array{Integer}(undef,0) - V = FESpace(model,reffe,conformity=DivConformity()) + test_single_field_fe_space(V) U = TrialFESpace(V,u) reffe = ReferenceFE(lagrangian,Float64,order) Q = TestFESpace(model,reffe,conformity=:L2) P = TrialFESpace(Q) - v(x) = VectorValue(-0.5*x[1]+1.0,-0.5*x[2],-0.5*x[3]) - vh = interpolate(v,V) - - e = v - vh + v2(x) = VectorValue(-0.5*x[1]+1.0,-0.5*x[2]) + vh = interpolate(v2,V) + e = v2 - vh Ω = Triangulation(model) dΩ = Measure(Ω,2*order) @@ -118,6 +112,61 @@ end # #writevtk(trian,"test",order=3,cellfields=["vh"=>vh]) + order = 1 + + reffe = ReferenceFE(HEX,raviart_thomas,order) + + domain = (0,1,0,2,-1,2) + partition = (3,3,2) + model = CartesianDiscreteModel(domain,partition) + + V = FESpace(model,reffe,conformity=DivConformity()) + test_single_field_fe_space(V) + U = TrialFESpace(V,u) + + reffe = ReferenceFE(lagrangian,Float64,order) + Q = TestFESpace(model,reffe,conformity=:L2) + P = TrialFESpace(Q) + + v3(x) = VectorValue(-0.5*x[1]+1.0,-0.5*x[2],-0.5*x[3]) + vh = interpolate(v3,V) + e = v3 - vh + + Ω = Triangulation(model) + dΩ = Measure(Ω,2*order) + + el2 = sqrt(sum( ∫( e⋅e )*dΩ )) + @test el2 < 1.0e-10 + + test_div_v_q_equiv(U,V,P,Q,Ω) + + + reffe = ReferenceFE(TET,raviart_thomas,order) + + domain = (0,1,0,2,-1,2) + partition = (3,3,2) + model = simplexify(CartesianDiscreteModel(domain,partition)) + + V = FESpace(model,reffe,conformity=DivConformity()) + test_single_field_fe_space(V) + U = TrialFESpace(V,u) + + reffe = ReferenceFE(lagrangian,Float64,order) + Q = TestFESpace(model,reffe,conformity=:L2) + P = TrialFESpace(Q) + + v3(x) = VectorValue(-0.5*x[1]+1.0,-0.5*x[2],-0.5*x[3]) + vh = interpolate(v3,V) + e = v3 - vh + + Ω = Triangulation(model) + dΩ = Measure(Ω,2*order) + + el2 = sqrt(sum( ∫( e⋅e )*dΩ )) + @test el2 < 1.0e-10 + + test_div_v_q_equiv(U,V,P,Q,Ω) + # Tests on manifold # Create domain @@ -135,7 +184,7 @@ end degree = 1 reffe_rt = ReferenceFE(raviart_thomas,Float64,order) - V = FESpace(Dc2Dp3model, reffe_rt ; conformity=:HDiv) + V = FESpace(Dc2Dp3model, reffe_rt; conformity=:HDiv) U = TrialFESpace(V,u) reffe = ReferenceFE(lagrangian,Float64,order) Q = TestFESpace(Dc2Dp3model,reffe,conformity=:L2) @@ -162,7 +211,7 @@ order = 1 u(x) = x -reffe = ReferenceFE(bdm,order) +reffe = ReferenceFE(TRI,bdm,order) V = TestFESpace(model,reffe,dirichlet_tags = [1,6]) test_single_field_fe_space(V) @@ -227,7 +276,7 @@ Dc2Dp3model = DiscreteModelPortion(DiscreteModel(Polytope{2},model),Γface_to_bg order = 1 degree = 2 -reffe_bdm = ReferenceFE(bdm,Float64,order) +reffe_bdm = ReferenceFE(TRI,bdm,Float64,order) V = FESpace(Dc2Dp3model, reffe_bdm ; conformity=:HDiv) U = TrialFESpace(V,u) reffe = ReferenceFE(lagrangian,Float64,order) diff --git a/test/FESpacesTests/PhysicalFESpacesTests.jl b/test/FESpacesTests/PhysicalFESpacesTests.jl index 40436a5db..c95c6deec 100644 --- a/test/FESpacesTests/PhysicalFESpacesTests.jl +++ b/test/FESpacesTests/PhysicalFESpacesTests.jl @@ -49,4 +49,15 @@ uh = interpolate(u,V) e = u - uh @test sqrt(sum(∫(e*e)*dΩ)) < 10e-8 +# Checking that dofs and shapfuns from a reffe with a dof prebasis (and shapefun=prebasis) can be evaluated in physical space + +order = 3 +reffe = ReferenceFE(modalC0,Float64,order) +V = FESpace(model,reffe,conformity=:H1) + +shfns_g = get_fe_basis(V) +phys_shfns_g = change_domain(shfns_g,PhysicalDomain()) +dofs_f = get_fe_dof_basis(V) +@test_nowarn dofs_f(phys_shfns_g) + end #module diff --git a/test/FieldsTests/FieldArraysTests.jl b/test/FieldsTests/FieldArraysTests.jl index 78f8177e4..d0b591dcb 100644 --- a/test/FieldsTests/FieldArraysTests.jl +++ b/test/FieldsTests/FieldArraysTests.jl @@ -8,7 +8,7 @@ using Test # Testing the default interface for field arrays -function result(f,x) +function result(f,x) T = return_type(testitem(f),testitem(x)) r = zeros(T,size(x)...,size(f)...) for j in CartesianIndices(f) @@ -29,7 +29,7 @@ f = MockField.(v) fp = v ∇fp = fill(zero(TensorValue{2,2,Float64}),length(v)) -∇∇fp = fill(zero(ThirdOrderTensorValue{2,2,2,Float64,6}),length(v)) +∇∇fp = fill(zero(ThirdOrderTensorValue{2,2,2,Float64}),length(v)) test_field_array(f,p,fp) test_field_array(f,p,fp,grad=∇fp) test_field_array(f,p,fp,grad=∇fp,gradgrad=∇∇fp) @@ -90,7 +90,7 @@ f = MockField.(v) fp = v ∇fp = fill(zero(TensorValue{2,2,Float64}),length(v)) -∇∇fp = fill(zero(ThirdOrderTensorValue{2,2,2,Float64,6}),length(v)) +∇∇fp = fill(zero(ThirdOrderTensorValue{2,2,2,Float64}),length(v)) test_field_array(f,p,fp) test_field_array(f,p,fp,grad=∇fp) test_field_array(f,p,fp,grad=∇fp,gradgrad=∇∇fp) @@ -110,7 +110,7 @@ f = MockFieldArray(v) fp = v ∇fp = fill(zero(TensorValue{2,2,Float64}),length(v)) -∇∇fp = fill(zero(ThirdOrderTensorValue{2,2,2,Float64,6}),length(v)) +∇∇fp = fill(zero(ThirdOrderTensorValue{2,2,2,Float64}),length(v)) test_field_array(f,p,fp) test_field_array(f,p,fp,grad=∇fp) diff --git a/test/FieldsTests/FieldInterfacesTests.jl b/test/FieldsTests/FieldInterfacesTests.jl index b24c10a8e..214b0fbf9 100644 --- a/test/FieldsTests/FieldInterfacesTests.jl +++ b/test/FieldsTests/FieldInterfacesTests.jl @@ -15,7 +15,7 @@ v = VectorValue(1.0,1.0) f = MockField(v) fp = v ∇fp = zero(TensorValue{2,2,Float64}) -∇∇fp = zero(ThirdOrderTensorValue{2,2,2,Float64,6}) +∇∇fp = zero(ThirdOrderTensorValue{2,2,2,Float64,8}) test_field(f,p,fp) test_field(f,p,fp,grad=∇fp) test_field(f,p,fp,grad=∇fp,gradgrad=∇∇fp) @@ -181,7 +181,7 @@ f = ConstantField(v) fp = v ∇fp = zero(TensorValue{2,2,Float64}) -∇∇fp = zero(ThirdOrderTensorValue{2,2,2,Float64,6}) +∇∇fp = zero(ThirdOrderTensorValue{2,2,2,Float64,8}) test_field(f,p,fp) test_field(f,p,fp,grad=∇fp) test_field(f,p,fp,grad=∇fp,gradgrad=∇∇fp) diff --git a/test/GridapTests/StokesMiniTests.jl b/test/GridapTests/StokesMiniTests.jl index f0fa4c195..4d6651094 100644 --- a/test/GridapTests/StokesMiniTests.jl +++ b/test/GridapTests/StokesMiniTests.jl @@ -27,7 +27,7 @@ add_tag_from_tags!(labels, "dirichlet", [1, 2, 5]) add_tag_from_tags!(labels, "neumann", [3, 4, 6, 7, 8]) reffe_u = ReferenceFE(lagrangian, VectorValue{2, Float64}, 1) -reffe_b = ReferenceFE(bubble, VectorValue{2, Float64}) +reffe_b = ReferenceFE(bubble, VectorValue{2, Float64}, 1) reffe_p = ReferenceFE(lagrangian, Float64, 1) V = TestFESpace(model, reffe_u, labels = labels, dirichlet_tags = "dirichlet", conformity = :H1) @@ -75,4 +75,4 @@ tol = 1.0e-10 @test eu_h1 < tol @test ep_l2 < tol -end # module \ No newline at end of file +end # module diff --git a/test/HelpersTests/HelperFunctionsTests.jl b/test/HelpersTests/HelperFunctionsTests.jl index 98b8dacd7..0382840d9 100644 --- a/test/HelpersTests/HelperFunctionsTests.jl +++ b/test/HelpersTests/HelperFunctionsTests.jl @@ -9,4 +9,26 @@ end @test 1 == get_val_parameter(Val{1}()) +module MockModule + const C1 = nothing + const C2 = C1 # Alias + const C3 = nothing + + export C1 + export C2 + #public C3 # would only work if we stop testing on VERSION < 1.11... +end + +change_link=Dict(:C2 => "C1") +s = public_names_in_md(MockModule; change_link) +@test s == """ + ### Exported names + + [`C1`](@ref), [`C2`](@ref C1),""" + + # ### Other public names + + # [`C3`](@ref), + # """ + end # module diff --git a/test/MultiFieldTests/MultiFieldCellFieldsTests.jl b/test/MultiFieldTests/MultiFieldCellFieldsTests.jl index 17505bd9e..c6db85fae 100644 --- a/test/MultiFieldTests/MultiFieldCellFieldsTests.jl +++ b/test/MultiFieldTests/MultiFieldCellFieldsTests.jl @@ -65,7 +65,7 @@ X = MultiFieldFESpace([U,P]) dv, dq = get_fe_basis(Y) du, dp = get_trial_fe_basis(X) -display(dv) +#display(dv) # sum of single spaces degree = 3 diff --git a/test/MultiFieldTests/MultiFieldFEFunctionsTests.jl b/test/MultiFieldTests/MultiFieldFEFunctionsTests.jl index c9276d155..fe7b35049 100644 --- a/test/MultiFieldTests/MultiFieldFEFunctionsTests.jl +++ b/test/MultiFieldTests/MultiFieldFEFunctionsTests.jl @@ -30,7 +30,7 @@ X = MultiFieldFESpace([U,P]) free_values = rand(num_free_dofs(X)) xh = FEFunction(X,free_values) -display(xh) +#display(xh) test_fe_function(xh) uh, ph = xh diff --git "a/test/PolynomialsTests/BarycentricP\316\233Bases.jl" "b/test/PolynomialsTests/BarycentricP\316\233Bases.jl" new file mode 100644 index 000000000..0f421627b --- /dev/null +++ "b/test/PolynomialsTests/BarycentricP\316\233Bases.jl" @@ -0,0 +1,155 @@ +module BarycentricPΛBasisTests + +using Test +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Arrays +using Gridap.Polynomials +using Gridap.Helpers +using ForwardDiff +using StaticArrays + +using Gridap.Polynomials: _combination_index, bernstein_term_id + +r = 3 # all possible bubble spaces are non empty + +# Bubble indices validation + +D = 3 +N = D+1 +for k in 0:D + w_prev = 0 + for (F, bubble_functions) in PmΛ_bubbles(r,k,D) + @test issorted(F) && (k ≤ length(F) ≤ N) && (F ⊆ 1:N) || (k,D,F) + + passed = true + for (w, α, α_id, J, sub_J_ids, sup_α_ids) in bubble_functions + passed == passed && w == w_prev+1 + w_prev = w + + passed = passed && all(α .≥ 0) && sum(α)==r-1 && length(α)==N && + α_id == bernstein_term_id(α) && + all( bernstein_term_id( [α[j]+Int(i==j) for j in eachindex(α)] ) == αpi_id + for (i,αpi_id) in enumerate(sup_α_ids) ) + + passed = passed && issorted(J) && length(J)==k+1 && (J ⊆ 1:N) && + all( _combination_index(J[J .≠ J[i]]) == Jsi_id for (i,Jsi_id) in enumerate(sub_J_ids) ) + end + @test passed || (r, k, D, F, bubble_functions) + end + @test w_prev == binomial(r+k-1,k)*binomial(D+r,D-k) +end + +for k in 0:D + w_prev = 0 + for (F, bubble_functions) in PΛ_bubbles(r,k,D) + @test issorted(F) && (k ≤ length(F) ≤ N) && (F ⊆ 1:N) || (k,D,F) + + passed = true + for (w, α, α_id, J) in bubble_functions + passed == passed && w == w_prev+1 + w_prev = w + + passed = passed && all(α .≥ 0) && sum(α)==r && length(α)==N && + α_id == bernstein_term_id(α) + + passed = passed && issorted(J) && length(J)==k && (J ⊆ 1:N) + end + @test passed || (r, k, D, F, bubble_functions) + end + @test w_prev == binomial(r+k,k)*binomial(D+r,D-k) +end + + +# Bases tests + +function _test_testvalue(b, Bx, Gx, Hx) + b0 = testvalue(b) + @test b0 isa typeof(b) + + @test evaluate(b0,x) isa typeof(Bx) + @test evaluate(Broadcasting(∇)(b0),x) isa typeof(Gx) + @test evaluate(Broadcasting(∇∇)(b0),x) isa typeof(Hx) +end + +function _test_basis(VD::Val{D}, T, r, k, vertices) where D + for PΛB in (BarycentricPmΛBasis, BarycentricPΛBasis) + b = PΛB(VD,T,r,k) + @test contains(sprint(show, MIME"text/plain"(), b._indices), "PᵣΛᵏ(△ᴰ) basis indices, r=$r k=$k D=$D") + @test_nowarn print_indices(b,IOBuffer()) + @test get_orders(b) == tfill(r,Val(D)) + + b2 = PΛB(VD,T,r,k; indices=b._indices) # indices recycling + @test b == b2 + @test b2._indices == b._indices + + faces = [bubble[1] for bubble in get_bubbles(b)] # bubble space selection + b2 = PΛB(b, faces...) + @test b == b2 + + Bx = evaluate(b,x) + Gx = evaluate(Broadcasting(∇)(b),x) + Hx = evaluate(Broadcasting(∇∇)(b),x) + _test_testvalue(b, Bx, Gx, Hx) + + bv = PΛB(VD,T,r,k,vertices) + Bx = evaluate(bv,x) + Gx = evaluate(Broadcasting(∇)(bv),x) + Hx = evaluate(Broadcasting(∇∇)(bv),x) + _test_testvalue(bv, Bx, Gx, Hx) + end +end + +T = Float64 + +# 0D 0D # +D = 0 +vertices = [Point{D,T}()] +x = [vertices[1]] +k = 0 + +_test_basis(Val(D), T, r, k, vertices) + +# 1D 1D # +D = 1 +Pt = Point{D,T} +vertices = [Pt(0.),Pt(1.)] +x = [xi for xi in vertices] + +for k in 0:D + _test_basis(Val(D), T, r, k, vertices) +end + + +# 2D 2D # +D = 2 +Pt = Point{D,T} +vertices = [Pt(0., 0),Pt(1.,0),Pt(0.,1.)] +x = [xi for xi in vertices] + +for k in 0:D + _test_basis(Val(D), T, r, k, vertices) +end + +# 3D 3D # +D = 3 +Pt = Point{D,T} +vertices = [Pt(0.,0,0),Pt(1.,0,0),Pt(0,1.,0),Pt(0,0,1.)] +x = [xi for xi in vertices] + +for k in 0:D + _test_basis(Val(D), T, r, k, vertices) +end + +# 4D 4D # +D = 4 +Pt = Point{D,T} +vertices = [Pt(0.,0,0,0),Pt(1.,0,0,0),Pt(0,1.,0,0),Pt(0,0,1.,0),Pt(0,0,0,1.)] +x = [xi for xi in vertices] + +for k in 0:D + k == 2 && continue # no vector proxy + _test_basis(Val(D), T, r, k, vertices) +end + +end # module diff --git a/test/PolynomialsTests/BernsteinBasesTests.jl b/test/PolynomialsTests/BernsteinBasesTests.jl new file mode 100644 index 000000000..07c2699b5 --- /dev/null +++ b/test/PolynomialsTests/BernsteinBasesTests.jl @@ -0,0 +1,348 @@ +module BernsteinBasisTests + +using Test +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Arrays +using Gridap.Polynomials +using ForwardDiff +using StaticArrays + +@test isHierarchical(Bernstein) == false + +x = [Point(0.),Point(1.),Point(.4)] +x1 = x[1] + +##################################### +# Tests for 1D Bernstein polynomial # +##################################### + +V = Float64 +G = gradient_type(V,x1) +H = gradient_type(G,x1) + +function test_internals(order,x,bx,Gbx,Hbx) + sz = (1,order+1) + for (i,xi) in enumerate(x) + v2 = zeros(sz) + Polynomials._evaluate_1d!(Bernstein,order,v2,xi,1) + @test all( [ bxi[1]≈vxi[1] for (bxi,vxi) in zip(bx[i,:],v2[:,1]) ] ) + + g2 = zeros(sz) + Polynomials._gradient_1d!(Bernstein,order,g2,xi,1) + @test all( [ bxi[1]≈vxi[1] for (bxi,vxi) in zip(Gbx[i,:],g2[:,1]) ] ) + + h2 = zeros(sz) + Polynomials._hessian_1d!(Bernstein,order,h2,xi,1) + @test all( [ bxi[1]≈vxi[1] for (bxi,vxi) in zip(Hbx[i,:],h2[:,1]) ] ) + end +end + + +# order 0 degenerated case + +order = 0 +b = BernsteinBasis(Val(1),V,order) +@test get_order(b) == 0 +@test get_exponents(b) == [(0,),] +@test testvalue(typeof(b)) isa typeof(b) + +bx = [ 1.; 1.; 1.;; ] +Gbx = G[ (0.,); (0.,); (0.,);; ] +Hbx = H[ (0.,); (0.,); (0.,);; ] + +test_internals(order,x,bx,Gbx,Hbx) + +test_field_array(b,x,bx,grad=Gbx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=Gbx[1,:],gradgrad=Hbx[1,:]) + + +# Order 1 + +order = 1 +b = BernsteinBasis(Val(1),V,order) + +bx = [ 1.0 0. + 0. 1.0 + 0.6 0.4] + +Gbx = G[ (-1.,) (1.,) + (-1.,) (1.,) + (-1.,) (1.,) ] + +Hbx = H[ (0.,) (0.,) + (0.,) (0.,) + (0.,) (0.,) ] + +test_internals(order,x,bx,Gbx,Hbx) + +test_field_array(b,x,bx,grad=Gbx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=Gbx[1,:],gradgrad=Hbx[1,:]) + + +# Order 2 + +order = 2 +b = BernsteinBasis(Val(1),V,order) + +bx = [ 1.0 0. 0. + 0. 0. 1.0 + 0.36 0.48 0.16] + + +Gbx = G[( -2.,) ( 2. ,) (0.,) + ( 0.,) (-2. ,) (2.,) + (-1.2,) ( 0.4,) (.8,) ] + +Hbx = H[ (2.,) (-4.,) (2.,) + (2.,) (-4.,) (2.,) + (2.,) (-4.,) (2.,) ] + +test_internals(order,x,bx,Gbx,Hbx) + +test_field_array(b,x,bx,≈, grad=Gbx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=Gbx[1,:],gradgrad=Hbx[1,:]) + + +# Order 3 + +function bernstein(K,N) + b = ntuple( i -> binomial(K,i-1), Val(K+1)) # all binomial(i,K) for 0≤i≤K + t -> b[N+1]*(t^N)*((1-t)^(K-N)) +end +_∇(b) = t -> ForwardDiff.derivative(b, t) +_H(b) = t -> ForwardDiff.derivative(y -> ForwardDiff.derivative(b, y), t) + +_bx_1D( order,x) = [ bernstein(order,n)( xi[1]) for xi in x, n in 0:order] +_Gbx_1D(order,x,G) = [ G(_∇(bernstein(order,n))(xi[1])) for xi in x, n in 0:order] +_Hbx_1D(order,x,H) = [ H(_H(bernstein(order,n))(xi[1])) for xi in x, n in 0:order] + +order = 3 +b = BernsteinBasis(Val(1),V,order) + +# x=x^1; x2 = x^2; x3 = x^3 +# -x3+3x2-3x+1 3x3-6x2+3x -3x3+3x2 x3 +bx = _bx_1D( order,x) +# -3x2+6x-3 9x2-12x+3 -9x2+6x 3x2 +Gbx = _Gbx_1D(order,x,G) +# -6x+6 18x-12 -18x+6 6x +Hbx = _Hbx_1D(order,x,H) +test_internals(order,x,bx,Gbx,Hbx) + +test_field_array(b,x,bx,≈, grad=Gbx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=Gbx[1,:],gradgrad=Hbx[1,:]) + + +# Order 4 + +order = 4 +b = BernsteinBasis(Val(1),V,order) + +bx = _bx_1D( order,x) +Gbx = _Gbx_1D(order,x,G) +Hbx = _Hbx_1D(order,x,H) + +test_internals(order,x,bx,Gbx,Hbx) + +test_field_array(b,x,bx,≈, grad=Gbx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=Gbx[1,:],gradgrad=Hbx[1,:]) + + +##################################### +# Tests for ND Bernstein polynomial # +##################################### + +function bernstein_nD(D,K,x2λ=nothing) + terms = Polynomials.bernstein_terms(K,D) + N = length(terms) + x -> begin + @assert length(x) == D + vals = zeros(eltype(x),N) + # change to barycentric coords of reference simplex + if isnothing(x2λ) + λ = SVector{D+1,eltype(x)}(1 - sum(x), x...) + else + λ = x2λ*SVector(1, x...) + end + for i in 1:N + vals[i] = Polynomials.multinomial(terms[i]...) + for (λi,ei) in zip(λ,terms[i]) + vals[i] *= λi^ei + end + end + return vals + end +end + +_∇(b) = x -> ForwardDiff.jacobian(b, get_array(x)) +_H(b) = x -> ForwardDiff.jacobian(y -> ForwardDiff.jacobian(b, y), get_array(x)) + +_bx( D,order,x, x2λ=nothing) = transpose(reduce(hcat, ( bernstein_nD(D,order,x2λ )(xi) for xi in x))) +_Gbx(D,order,x,G,x2λ=nothing) = transpose(reduce(hcat, ( map(G, eachrow( _∇(bernstein_nD(D,order,x2λ))(xi))) for xi in x))) +_Hbx(D,order,x,H,x2λ=nothing) = transpose(reduce(hcat, ( map(x->H(x...), eachrow(reshape(_H(bernstein_nD(D,order,x2λ))(xi), :,D*D))) for xi in x))) + +D = 2 +x = [Point(1.,0.), Point(.0,.5), Point(1.,.5), Point(.2,.3)] +x3 = x[3] + +# scalar valued in 2D +V = Float64 +G = gradient_type(V,x3) +H = gradient_type(G,x3) + +order = 0 +b = BernsteinBasisOnSimplex(Val(D),V,order) +@test get_order(b) == 0 +bx = _bx( D,order,x) +Gbx = _Gbx(D,order,x,G) +Hbx = _Hbx(D,order,x,H) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) + +order = 1 +b = BernsteinBasisOnSimplex(Val(D),V,order) +bx = _bx( D,order,x) +Gbx = _Gbx(D,order,x,G) +Hbx = _Hbx(D,order,x,H) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) + +order = 2 +b = BernsteinBasisOnSimplex(Val(D),V,order) +bx = _bx( D,order,x) +Gbx = _Gbx(D,order,x,G) +Hbx = _Hbx(D,order,x,H) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) + +order = 3 +b = BernsteinBasisOnSimplex(Val(D),V,order) +bx = _bx( D,order,x) +Gbx = _Gbx(D,order,x,G) +Hbx = _Hbx(D,order,x,H) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) + +order = 4 +b = BernsteinBasisOnSimplex(Val(D),V,order) +bx = _bx( D,order,x) +Gbx = _Gbx(D,order,x,G) +Hbx = _Hbx(D,order,x,H) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],≈,grad=Gbx[1,:],gradgrad=Hbx[1,:]) + +b_terms = bernstein_terms(order,D) +λ = Polynomials._cart_to_bary(x3, b.cart_to_bary_matrix) +for j in 1:length(b) + α = b_terms[j] + id_α = bernstein_term_id(α) + @test id_α == j + c = Float64[ Int(i==id_α) for i in 1:length(b) ] # Bernstein coefficients of Bα + Polynomials._de_Casteljau_nD!(c, λ, Val(order), Val(D)) + Bα_x3 = c[1] + @test Bα_x3 == bx[3,id_α] +end + +# Vector valued in 2D +V = VectorValue{3,Float64} +G = gradient_type(V,x3) +H = gradient_type(G,x3) + +order = 2 +b = BernsteinBasisOnSimplex(Val(D),V,order) +@test testvalue(typeof(b)) isa typeof(b) +bx = V[(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.) (1., 0., 0.) (0., 1., 0.) (0., 0., 1.) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.); + (0.25, 0., 0.) (0., 0.25, 0.) (0., 0., 0.25) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0.5, 0., 0.) (0., 0.5, 0.) (0., 0., 0.5) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0., 0., 0.) (0.25, 0., 0.) (0., 0.25, 0.) (0., 0., 0.25); + (0.25, 0., 0.) (0., 0.25, 0.) (0., 0., 0.25) (-1., 0., 0.) (0., -1., 0.) (0., 0., -1.) (-0.5, 0., 0.) (0., -0.5, 0.) (0., 0., -0.5) (1., 0., 0.) (0., 1., 0.) (0., 0., 1.) (1., 0., 0.) (0., 1., 0.) (0., 0., 1.) (0.25, 0., 0.) (0., 0.25, 0.) (0., 0., 0.25); (0.25, 0., 0.) (0., 0.25, 0.) (0., 0., 0.25) (0.2, 0., 0.) (0., 0.2, 0.) (0., 0., 0.2) (0.3, 0., 0.) (0., 0.3, 0.) (0., 0., 0.3) (0.04000000000000001, 0., 0.) (0., 0.04000000000000001, 0.) (0., 0., 0.04000000000000001) (0.12, 0., 0.) (0., 0.12, 0.) (0., 0., 0.12) (0.09, 0., 0.) (0., 0.09, 0.) (0., 0., 0.09)] +Gbx = G[(0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0.) (-2., -2., 0., 0., 0., 0.) (0., 0., -2., -2., 0., 0.) (0., 0., 0., 0., -2., -2.) (0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0.) (2., 0., 0., 0., 0., 0.) (0., 0., 2., 0., 0., 0.) (0., 0., 0., 0., 2., 0.) (0., 2., 0., 0., 0., 0.) (0., 0., 0., 2., 0., 0.) (0., 0., 0., 0., 0., 2.) (0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0.); + (-1., -1., 0., 0., 0., 0.) (0., 0., -1., -1., 0., 0.) (0., 0., 0., 0., -1., -1.) (1., 0., 0., 0., 0., 0.) (0., 0., 1., 0., 0., 0.) (0., 0., 0., 0., 1., 0.) (-1., 0., 0., 0., 0., 0.) (0., 0., -1., 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.) (1., 0., 0., 0., 0., 0.) (0., 0., 1., 0., 0., 0.) (0., 0., 0., 0., 1., 0.) (0., 1., 0., 0., 0., 0.) (0., 0., 0., 1., 0., 0.) (0., 0., 0., 0., 0., 1.); + (1., 1., 0., 0., 0., 0.) (0., 0., 1., 1., 0., 0.) (0., 0., 0., 0., 1., 1.) (-3.0, -2., 0., 0., 0., 0.) (0., 0., -3.0, -2., 0., 0.) (0., 0., 0., 0., -3.0, -2.) (-1., -2., 0., 0., 0., 0.) (0., 0., -1., -2., 0., 0.) (0., 0., 0., 0., -1., -2.) (2., 0., 0., 0., 0., 0.) (0., 0., 2., 0., 0., 0.) (0., 0., 0., 0., 2., 0.) (1., 2., 0., 0., 0., 0.) (0., 0., 1., 2., 0., 0.) (0., 0., 0., 0., 1., 2.) (0., 1., 0., 0., 0., 0.) (0., 0., 0., 1., 0., 0.) (0., 0., 0., 0., 0., 1.); + (-1., -1., 0., 0., 0., 0.) (0., 0., -1., -1., 0., 0.) (0., 0., 0., 0., -1., -1.) (0.6, -0.4, 0., 0., 0., 0.) (0., 0., 0.6, -0.4, 0., 0.) (0., 0., 0., 0., 0.6, -0.4) (-0.6, 0.4, 0., 0., 0., 0.) (0., 0., -0.6, 0.4, 0., 0.) (0., 0., 0., 0., -0.6, 0.4) (0.4, 0., 0., 0., 0., 0.) (0., 0., 0.4, 0., 0., 0.) (0., 0., 0., 0., 0.4, 0.) (0.6, 0.4, 0., 0., 0., 0.) (0., 0., 0.6, 0.4, 0., 0.) (0., 0., 0., 0., 0.6, 0.4) (0., 0.6, 0., 0., 0., 0.) (0., 0., 0., 0.6, 0., 0.) (0., 0., 0., 0., 0., 0.6)] +Hbx = H[(2., 2., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 2., 2., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 2., 2.) (-4.0, -2., -2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., -4.0, -2., -2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., -4.0, -2., -2., 0.) (0., -2., -2., -4.0, 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., -2., -2., -4.0, 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., -2., -2., -4.0) (2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0.) (0., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 2., 2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 0.) (0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 2.); + (2., 2., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 2., 2., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 2., 2.) (-4.0, -2., -2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., -4.0, -2., -2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., -4.0, -2., -2., 0.) (0., -2., -2., -4.0, 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., -2., -2., -4.0, 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., -2., -2., -4.0) (2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0.) (0., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 2., 2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 0.) (0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 2.); + (2., 2., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 2., 2., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 2., 2.) (-4.0, -2., -2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., -4.0, -2., -2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., -4.0, -2., -2., 0.) (0., -2., -2., -4.0, 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., -2., -2., -4.0, 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., -2., -2., -4.0) (2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0.) (0., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 2., 2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 0.) (0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 2.); + (2., 2., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 2., 2., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 2., 2.) (-4.0, -2., -2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., -4.0, -2., -2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., -4.0, -2., -2., 0.) (0., -2., -2., -4.0, 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., -2., -2., -4.0, 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., -2., -2., -4.0) (2., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0.) (0., 2., 2., 0., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 2., 2., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 2., 2., 0.) (0., 0., 0., 2., 0., 0., 0., 0., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 2., 0., 0., 0., 0.) (0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 2.)] +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=Gbx[1,:],gradgrad=Hbx[1,:]) + +# scalar valued in 3D + +x = [Point(0.,0.,1.), Point(.5,.5,.5), Point(1.,.2,.4), Point(.2,.4,.3)] +x1 = x[1] + +V = Float64 +G = gradient_type(V,x1) +H = gradient_type(G,x1) + +order = 4 +D = 3 +b = BernsteinBasisOnSimplex(Val(D),V,order) +bx = _bx( D,order,x) +Gbx = _Gbx(D,order,x,G) +Hbx = _Hbx(D,order,x,H) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=Gbx[1,:],gradgrad=Hbx[1,:]) + + +############################################################################ +# Tests for ND Bernstein polynomial with arbitrary barycentric coordinates # +############################################################################ +# +T = Float64 + +D = 0 +vertices = (Point(), ) +b = BernsteinBasisOnSimplex(Val(D), Float64, 3, vertices) + +D = 2 + +vertices = (Point(0.,1.), Point(0.,2.), Point(0.,3.)) +@test_throws DomainError Polynomials._compute_cart_to_bary_matrix(vertices, Val(D+1)) + +vertices = (Point(5.,0.), Point(7.,2.), Point(0.,3.)) + +b = BernsteinBasisOnSimplex(Val(D), Float64, 3, vertices) +x = [Point(.0,.5), Point(1.,.5), Point(.2,.3), Point(5.,0.), Point(7.,2.), Point(0.,3.)] +x1 = x[1] + +for xi in x + λi = Polynomials._cart_to_bary(xi, b.cart_to_bary_matrix) + @test sum(λi) ≈ 1. + @test xi ≈ sum(λi .* vertices) +end + + +# Scalar value in 2D +D = 2 +vertices = (Point(5.,0.), Point(7.,2.), Point(0.,3.)) +x2λ = Polynomials._compute_cart_to_bary_matrix(vertices, Val(D+1)) + +G = gradient_type(V,x1) +H = gradient_type(G,x1) + +order = 4 +b = BernsteinBasisOnSimplex(Val(D),V,order,vertices) +bx = _bx( D,order,x, x2λ) +Gbx = _Gbx(D,order,x,G,x2λ) +Hbx = _Hbx(D,order,x,H,x2λ) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) +test_field_array(b,x1,bx[1,:],≈,grad=Gbx[1,:],gradgrad=Hbx[1,:]) + + +# scalar valued in 3D +D = 3 +vertices = (Point(5.,0.,0.), Point(7.,0.,2.), Point(0.,3.,3.), Point(3.,0.,3.)) +x2λ = Polynomials._compute_cart_to_bary_matrix(vertices, Val(D+1)) + +x = [Point(0.,0.,1.), Point(.5,.5,.5), Point(1.,.2,.4), Point(.2,.4,.3)] +x1 = x[1] + +V = Float64 +G = gradient_type(V,x1) +H = gradient_type(G,x1) + +order = 4 +b = BernsteinBasisOnSimplex(Val(D),V,order,vertices) +bx = _bx( D,order,x, x2λ) +Gbx = _Gbx(D,order,x,G,x2λ) +Hbx = _Hbx(D,order,x,H,x2λ) +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) +test_field_array(b,x1,bx[1,:],≈,grad=Gbx[1,:],gradgrad=Hbx[1,:]) + +end # module diff --git a/test/PolynomialsTests/ChangeBasisTests.jl b/test/PolynomialsTests/ChangeBasisTests.jl index 9119d3609..09dee2b58 100644 --- a/test/PolynomialsTests/ChangeBasisTests.jl +++ b/test/PolynomialsTests/ChangeBasisTests.jl @@ -13,7 +13,7 @@ order = 1 V = Float64 G = gradient_type(V,xi) H = gradient_type(G,xi) -f = MonomialBasis{2}(V,order) +f = MonomialBasis(Val(2),V,order) change = inv(evaluate(f,nodes)) diff --git a/test/PolynomialsTests/ChebyshevBasesTests.jl b/test/PolynomialsTests/ChebyshevBasesTests.jl new file mode 100644 index 000000000..94e419557 --- /dev/null +++ b/test/PolynomialsTests/ChebyshevBasesTests.jl @@ -0,0 +1,120 @@ +module ChebyshevBasisTests + +using Test +using Gridap.Arrays +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Polynomials +using ForwardDiff + +@test isHierarchical(Chebyshev) == true + +np = 3 +x = [Point(2.),Point(3.),Point(4.)] +x1 = x[1] + + +# Only test 1D evaluations as tensor product structure is tested in monomial tests + +V = Float64 +G = gradient_type(V,x1) +H = gradient_type(G,x1) + +chebyshev_T(N) = t -> begin + ξ = 2t-1 + sq = sqrt(ξ*ξ-1) + .5*( (ξ - sq)^N + (ξ + sq)^N ) +end +chebyshev_U(N) = t -> begin + ξ = 2t-1 + sq = sqrt(ξ*ξ-1) + .5*( (ξ + sq)^(N+1) - (ξ - sq)^(N+1) )/sq +end +_∇(b) = t -> ForwardDiff.derivative(b, t) +_H(b) = t -> ForwardDiff.derivative(y -> ForwardDiff.derivative(b, y), t) + +###################################### +# First Kind Chebyshev ( Hessian TODO) +###################################### + +# order 0 degenerated case +order = 0 +b = ChebyshevBasis(Val(1),V,order) +@test get_order(b) == 0 +@test get_orders(b) == (0,) +@test testvalue(typeof(b)) isa typeof(b) + +bx = [ chebyshev_T(n)( xi[1]) for xi in x, n in 0:order] +∇bx = [ G(_∇(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] +Hbx = [ H(_H(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] + +test_field_array(b,x,bx,≈, grad=∇bx)#,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],≈,grad=∇bx[1,:])#,gradgrad=Hbx[1,:]) + + +# Order 1 +order = 1 +b = ChebyshevBasis(Val(1),V,order) + +bx = [ chebyshev_T(n)( xi[1]) for xi in x, n in 0:order] +∇bx = [ G(_∇(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] +Hbx = [ H(_H(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] + +test_field_array(b,x,bx,≈,grad=∇bx)#,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],≈,grad=∇bx[1,:])#,gradgrad=Hbx[1,:]) + + +# Order 2 +order = 2 +b = ChebyshevBasis(Val(1),V,order) + + +bx = [ chebyshev_T(n)( xi[1]) for xi in x, n in 0:order] +∇bx = [ G(_∇(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] +Hbx = [ H(_H(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] + +test_field_array(b,x,bx,≈,grad=∇bx)#,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],≈,grad=∇bx[1,:])#,gradgrad=Hbx[1,:]) + + +# Order 3 +order = 3 +b = ChebyshevBasis(Val(1),V,order) + +bx = [ chebyshev_T(n)( xi[1]) for xi in x, n in 0:order] +∇bx = [ G(_∇(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] +Hbx = [ H(_H(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] + +test_field_array(b,x,bx,≈, grad=∇bx)#,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],≈,grad=∇bx[1,:])#,gradgrad=Hbx[1,:]) + + +# Order 4 +order = 4 +b = ChebyshevBasis(Val(1),V,order) + +bx = [ chebyshev_T(n)( xi[1]) for xi in x, n in 0:order] +∇bx = [ G(_∇(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] +Hbx = [ H(_H(chebyshev_T(n))(xi[1])) for xi in x, n in 0:order] + +test_field_array(b,x,bx,≈,grad=∇bx)#,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],≈,grad=∇bx[1,:])#,gradgrad=Hbx[1,:]) + + +############################ +# Second Kind Chebyshev TODO +############################ + +@test_throws ErrorException ChebyshevBasis(Val(1),Float64,0;kind=:U) + +order = 1 +d = 1 +b = ChebyshevBasis(Val(1),V,order) +Hb = FieldGradientArray{2}(b) +r, c, g, h = return_cache(Hb,x) + +@test_throws ErrorException Polynomials._gradient_1d!(Chebyshev{:U},Val(order),g,x1,d) +@test_throws ErrorException Polynomials._hessian_1d!( Chebyshev{:U},Val(order),h,x1,d) + + +end # module diff --git a/test/PolynomialsTests/CompWiseTensorPolyBasesTests.jl b/test/PolynomialsTests/CompWiseTensorPolyBasesTests.jl new file mode 100644 index 000000000..b7b92f704 --- /dev/null +++ b/test/PolynomialsTests/CompWiseTensorPolyBasesTests.jl @@ -0,0 +1,63 @@ +module CompWiseTensorPolyBasisTests + +using Test +using Gridap.Arrays +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Polynomials + +PT = Bernstein +T = Float64 + +D = 2 +x = [Point(1.,0.), Point(.0,.5), Point(.2,.3)] + +orders = Int[ 3 0; 1 0] +V = VectorValue{2,T} +b = CompWiseTensorPolyBasis{D}(PT,V, orders) + +@test Tuple.(b.comp_terms[1]) == [ (1, 1), (2, 1), (3, 1), (4, 1)] +@test Tuple.(b.comp_terms[2]) == [ (1, 1), (2, 1), ] + +x₁ = [Point(1.), Point(.0), Point(.2)] +b_1x = BernsteinBasis(Val(1),T,3) +b_2x = BernsteinBasis(Val(1),T,1) +Gb_1x = Broadcasting(∇)(b_1x) +Gb_2x = Broadcasting(∇)(b_2x) +Hb_1x = Broadcasting(∇∇)(b_1x) +Hb_2x = Broadcasting(∇∇)(b_2x) + +bx = hcat( [ bi .* VectorValue(1.,0) for bi in evaluate(b_1x, x₁) ], + [ bi .* VectorValue(0.,1) for bi in evaluate(b_2x, x₁) ]) +Gbx = hcat( [ getindex.(bi,1) .* TensorValue(1.,0,0,0) for bi in evaluate(Gb_1x, x₁) ], + [ getindex.(bi,1) .* TensorValue(0.,0,1,0) for bi in evaluate(Gb_2x, x₁) ]) +Hbx = hcat( [ getindex.(bi,1) .* ThirdOrderTensorValue(1.,0,0,0,0,0,0,0) for bi in evaluate(Hb_1x, x₁) ], + [ getindex.(bi,1) .* ThirdOrderTensorValue(0.,0,0,0,1,0,0,0) for bi in evaluate(Hb_2x, x₁) ]) + +test_field_array(b,x,bx,≈, grad=Gbx, gradgrad=Hbx) + +# Dependent components + +V = SymTracelessTensorValue{2,T} +b = CompWiseTensorPolyBasis{D}(PT,V, orders) +evaluate(b,x) +evaluate(Broadcasting(∇)(b),x) + +@test Tuple.(b.comp_terms[1]) == [ (1, 1), (2, 1), (3, 1), (4, 1)] +@test Tuple.(b.comp_terms[2]) == [ (1, 1), (2, 1), ] + +x₁ = [Point(1.), Point(.0), Point(.2)] +b_1x = BernsteinBasis(Val(1),T,3) +b_2x = BernsteinBasis(Val(1),T,1) +Gb_1x = Broadcasting(∇)(b_1x) +Gb_2x = Broadcasting(∇)(b_2x) + +bx = hcat( [ bi .* V(1.,0) for bi in evaluate(b_1x, x₁) ], + [ bi .* V(0.,1) for bi in evaluate(b_2x, x₁) ]) +Gbx = hcat( [ getindex.(bi,1) .* VectorValue(1.,0)⊗V(1.,0) for bi in evaluate(Gb_1x, x₁) ], + [ getindex.(bi,1) .* VectorValue(1.,0)⊗V(0.,1) for bi in evaluate(Gb_2x, x₁) ]) + +test_field_array(b,x,bx,≈, grad=Gbx) + + +end # module diff --git a/test/PolynomialsTests/CurlConformBasesTests.jl b/test/PolynomialsTests/CurlConformBasesTests.jl new file mode 100644 index 000000000..27354b01d --- /dev/null +++ b/test/PolynomialsTests/CurlConformBasesTests.jl @@ -0,0 +1,222 @@ +module CurlConformBasesTests + +using Test +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Arrays +using Gridap.Polynomials + +PT = Monomial +T = Float64 +k = 1 + +################################################# +# Curl conform bases on n-cubes (non Bernstein) # +################################################# + +xi = Point(2,3) +np = 5 +x = fill(xi,np) + +# 3D +order = 0 +r = order+1 +D = 2 +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT) + +@test length(b) == 4 +@test get_order(b) == 1 +@test get_orders(b) == (1,1) +@test testvalue(typeof(b)) isa typeof(b) + +V = VectorValue{D,T} +@test_throws AssertionError FEEC_poly_basis(Val(D),V,r,k,:Q⁻,PT) + +xi = Point(2,3,5) +np = 5 +x = fill(xi,np) + +order = 0 +r = order+1 +D = 3 +T = Float64 +V = VectorValue{D,T} +G = gradient_type(V,xi) +H = gradient_type(G,xi) +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT) + +v = V[ +# (1.0, 0.0, 0.0), ( y , 0.0, 0.0), ( z , 0.0, 0.0), ( yz , 0.0, 0.0), +# (0.0, 1.0, 0.0), (0.0, x , 0.0), (0.0, z , 0.0), (0.0, xz , 0.0), +# (0.0, 0.0, 1.0), (0.0, 0.0, x ), (0.0, 0.0, y ), (0.0, 0.0, xy)] + (1.0, 0.0, 0.0), (3.0, 0.0, 0.0), (5.0, 0.0, 0.0), (15.0, 0.0, 0.0), + (0.0, 1.0, 0.0), (0.0, 2.0, 0.0), (0.0, 5.0, 0.0), (0.0, 10.0, 0.0), + (0.0, 0.0, 1.0), (0.0, 0.0, 2.0), (0.0, 0.0, 3.0), (0.0, 0.0, 6.0)] + +g = G[ + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( 1 , 0 , 0 ) + (0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( y , 0 , 0 ) + (0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( z , 0 , 0 ) + (0.0, 5.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( yz , 0 , 0 ) + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( 0 , 1 , 0 ) + (0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( 0 , x , 0 ) + (0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0), # ( 0 , z , 0 ) + (0.0, 0.0, 0.0, 5.0, 0.0, 2.0, 0.0, 0.0, 0.0), # ( 0 , xz, 0 ) + (0.0, 0.0, 0.0, 0.0, 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, 1.0, 0.0, 0.0), # ( 0 , 0 , x ) + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0), # ( 0 , 0 , y ) + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 2.0, 0.0)] # ( 0 , 0 , xy ) + +h = H[ + (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, 0.0, 0.0, 0.0, 0.0, 0.0, 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.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), # ( y , 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, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( z , 0 , 0 ) + (0.0, 0.0, 0.0, 0.0, 0.0, 1. , 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.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( yz , 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, 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 , 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.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( 0 , x , 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.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( 0 , z , 0 ) + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1. , 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 , xz, 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.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 , 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, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( 0 , 0 , x ) + (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, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), # ( 0 , 0 , y ) + (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, 0.0, 0.0, 0.0, 1. , 0.0, 1. , 0.0, 0.0, 0.0, 0.0, 0.0)] # ( 0 , 0 , xy ) + +bx = repeat(permutedims(v),np) +∇bx = repeat(permutedims(g),np) +Hbx = repeat(permutedims(h),np) + +evaluate(b,x) +evaluate(Broadcasting(∇)(b),x) +evaluate(Broadcasting(∇∇)(b),x) + +test_field_array(b,x,bx,grad=∇bx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:],gradgrad=Hbx[1,:]) + +# 2D + +xi = Point(2,3) +np = 5 +x = fill(xi,np) + +order = 1 +r = order+1 +D = 2 +V = VectorValue{D,T} +G = gradient_type(V,xi) +H = gradient_type(G,xi) +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT) +@test testvalue(typeof(b)) isa typeof(b) + +v = V[ +# (1., 0.), (x , 0.), (y , 0.), (xy, 0.), (y², 0.), (xy², 0.), +# (0., 1.), (0., x ), (0., x²), (0., y ), (0., xy), (0., x²y)] + (1., 0.), (2., 0.), (3., 0.), (6., 0.), (9., 0.), (18., 0.), + (0., 1.), (0., 2.), (0., 4.), (0., 3.), (0., 6.), (0., 12.)] + +g = G[ + # # ∂xV₁ ∂yV₁ ∂ₓV₂ ∂xV₂ # (V₁, V₂ ) + (0., 0., 0., 0.), # (0 , 0 , 0 , 0 ) # (1 , 0 ) + (1., 0., 0., 0.), # (1 , 0 , 0 , 0 ) # (x , 0 ) + (0., 1., 0., 0.), # (0 , 1 , 0 , 0 ) # (y , 0 ) + (3., 2., 0., 0.), # (y , x, 0 , 0 ) # (xy, 0 ) + (0., 6., 0., 0.), # (0., 2y, 0 , 0 ) # (y², 0 ) + (9., 12., 0., 0.), # (y², 2xy, 0 , 0 ) # (xy²,0 ) + (0., 0., 0., 0.), # (0 , 0 , 0 , 0 ) # (0 , 1 ) + (0., 0., 1., 0.), # (0 , 0 , 1 , 0 ) # (0 , x ) + (0., 0., 4., 0.), # (0 , 0 , 2x, 0 ) # (0 , x² ) + (0., 0., 0., 1.), # (0 , 0 , 0 , 1 ) # (0 , y ) + (0., 0., 3., 2.), # (0 , 0 , y, x ) # (0 , xy ) + (0., 0., 12., 4.)] # (0 , 0 , 2xy, x²) # (0 , x²y) + +h = H[ + # ∂xxV₁ ∂yxV₁ ∂xyV₁ ∂yyV₁ ∂xxV₂ ∂yxV₂ ∂xxV₂ ∂yxV₂ # (V₁, V₂ ) + (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 ), # (x , 0. ) # (0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) + (0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), # (y , 0. ) # (0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) + (0 , 1 , 1 , 0 , 0 , 0 , 0 , 0 ), # (xy, 0. ) # (0 , 1 , 1, 0, 0 , 0 , 0 , 0 ) + (0 , 0., 0 , 2 , 0 , 0 , 0 , 0 ), # (y², 0. ) # (0 , 0., 0, 2, 0 , 0 , 0 , 0 ) + (0 , 6., 6., 4., 0 , 0 , 0 , 0 ), # (xy², 0.) # (0 , 2y, 2y, 2x, 0 , 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., x ) # (0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) + (0 , 0 , 0 , 0 , 2 , 0 , 0 , 0 ), # (0., x² ) # (0 , 0 , 0 , 0 , 2 , 0 , 0 , 0 ) + (0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ), # (0., y ) # (0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) + (0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 ), # (0., xy ) # (0 , 0 , 0 , 0 , 0 , 1 , 1 , 0 ) + (0 , 0 , 0 , 0 , 6., 4., 4., 0 )] # (0., x²y) # (0 , 0 , 0 , 0 , 2y, 2x, 2x, 0 ) + +bx = repeat(permutedims(v),np) +∇bx = repeat(permutedims(g),np) +Hbx = repeat(permutedims(h),np) +test_field_array(b,x,bx,grad=∇bx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:],gradgrad=Hbx[1,:]) + + +# 1D + +order = 0 +r = order+1 +D = 1 +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT) + +@test b isa CartProdPolyBasis{D,T,PT} +@test testvalue(typeof(b)) isa typeof(b) + + +################################################### +# Curl conform bases on simplices (non Bernstein) # +################################################### + +xi = Point(2.,3.,5.) +np = 3 +x = fill(xi,np) + +order = 0 +D = 3 +b = NedelecPolyBasisOnSimplex{D}(PT, T, order) +@test testvalue(typeof(b)) isa typeof(b) + +V = VectorValue{D, Float64} +v = V[(1,0,0),(0,1,0),(0,0,1),(-3,2,0),(-5,0,2),(0,-5,3)] + +G = gradient_type(V,xi) +g = G[ + (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,-1,0, 1,0,0, 0,0,0),(0,0,-1, 0,0,0, 1,0,0),(0,0,0, 0,0,-1, 0,1,0)] + +bx = repeat(permutedims(v),np) +∇bx = repeat(permutedims(g),np) +test_field_array(b,x,bx,grad=∇bx) +test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) + +xi = Point(2.,3.) +np = 4 +x = fill(xi,np) + +order = 0 +D = 2 +b = NedelecPolyBasisOnSimplex{D}(PT, T, order) +@test testvalue(typeof(b)) isa typeof(b) +V = VectorValue{D, Float64} +v = V[(1,0),(0,1),(-3,2)] +G = gradient_type(V,xi) +g = G[(0,0, 0,0), (0,0, 0,0), (0,-1, 1,0)] +bx = repeat(permutedims(v),np) +∇bx = repeat(permutedims(g),np) +test_field_array(b,x,bx,grad=∇bx) +test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) + + +# 1D + +order = 0 +r = order+1 +D = 1 +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,PT) + +@test b isa CartProdPolyBasis{D,T,PT} +@test testvalue(typeof(b)) isa typeof(b) + +# D > 3 not implemented + +@test_throws ErrorException NedelecPolyBasisOnSimplex{4}(PT, T, order) + +end # module diff --git a/test/PolynomialsTests/DivConformBasesTests.jl b/test/PolynomialsTests/DivConformBasesTests.jl new file mode 100644 index 000000000..c9e1ede77 --- /dev/null +++ b/test/PolynomialsTests/DivConformBasesTests.jl @@ -0,0 +1,184 @@ +module DivConformBasesTests + +using Test +using Gridap.Arrays +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Polynomials + +PT = Monomial +T = Float64 + +################################################ +# Div conform bases on n-cubes (non Bernstein) # +################################################ + +xi = Point(2,3) +np = 5 +x = fill(xi,np) + +order = 0 +D = 2 +k, r, rotate_90 = D-1, order+1, D==2 +V = VectorValue{D,T} +G = gradient_type(V,xi) +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT; rotate_90) + +@test length(b) == 4 +@test get_order(b) == 1 +@test get_orders(b) == (1,1) +@test return_type(b) == V +@test testvalue(typeof(b)) isa typeof(b) + +@test_throws AssertionError FEEC_poly_basis(Val(D),V,r,k,:Q⁻,PT; rotate_90) + +xi = Point(2,3,5) +np = 5 +x = fill(xi,np) + +order = 0 +D = 3 +k, r, rotate_90 = D-1, order+1, D==2 +V = VectorValue{D,T} +G = gradient_type(V,xi) +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT; rotate_90) + +v = V[ + (1.0, 0.0, 0.0), (2.0, 0.0, 0.0), + (0.0, 1.0, 0.0), (0.0, 3.0, 0.0), + (0.0, 0.0, 1.0), (0.0, 0.0, 5.0)] + +g = G[ + (0.0, 0.0, 0.0, 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, 0.0, 0.0, 0.0, 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, 0.0), + (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)] + +bx = repeat(permutedims(v),np) +∇bx = repeat(permutedims(g),np) +test_field_array(b,x,bx,grad=∇bx) +test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) + +xi = Point(2,3) +np = 5 +x = fill(xi,np) + +order = 1 +D = 2 +k, r, rotate_90 = D-1, order+1, D==2 +V = VectorValue{D,T} +G = gradient_type(V,xi) +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT; rotate_90) + +v = V[ + ( 1., 0. ), ( 2., 0. ), ( 4., 0. ), ( 3., 0. ), ( 6., 0. ), (12., 0. ), + ( 0., 1. ), ( 0., 2. ), ( 0., 3. ), ( 0., 6. ), ( 0., 9. ), ( 0., 18.)] + +g = G[ + ( 0., 0., 0., 0.), + ( 1., 0., 0., 0.), + ( 4., 0., 0., 0.), + ( 0., 1., 0., 0.), + ( 3., 2., 0., 0.), + (12., 4., 0., 0.), + ( 0., 0., 0., 0.), + ( 0., 0., 1., 0.), + ( 0., 0., 0., 1.), + ( 0., 0., 3., 2.), + ( 0., 0., 0., 6.), + ( 0., 0., 9.,12.)] + +bx = repeat(permutedims(v),np) +∇bx = repeat(permutedims(g),np) +test_field_array(b,x,bx,grad=∇bx) +test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) + +# 1D + +order = 0 +D = 1 +k, r, rotate_90 = D-1, order+1, D==2 +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,PT; rotate_90) + +@test b isa CartProdPolyBasis{D,T,PT} +@test testvalue(typeof(b)) isa typeof(b) + + +################################################## +# Div conform bases on simplices (non Bernstein) # +################################################## + +xi = Point(4,2) +np = 1 +x = fill(xi,np) + +order = 2 +D = 2 +k, r, rotate_90 = D-1, order+1, D==2 +V = VectorValue{D,T} +G = gradient_type(V,xi) +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,PT; rotate_90) + +@test testvalue(typeof(b)) isa typeof(b) + +v = V[ + (1.0, 0.0), (4.0, 0.0), (16.0, 0.0), (2.0, 0.0), (8.0, 0.0), (4.0, 0.0), # pterm ex + (0.0, 1.0), (0.0, 4.0), (0.0, 16.0), (0.0, 2.0), (0.0, 8.0), (0.0, 4.0), # pterm ey + (64.0,32.0), (32.0,16.0), (16.0, 8.0)] # sterms + +g = G[ + (0.0, 0.0, 0.0, 0.0), (1.0, 0.0, 0.0, 0.0), (8.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (2.0, 4.0, 0.0, 0.0), (0.0, 4.0, 0.0, 0.0), # pterm ex + (0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, 0.0, 8.0, 0.0), (0.0, 0.0, 0.0, 1.0), (0.0, 0.0, 2.0, 4.0), (0.0, 0.0, 0.0, 4.0), # pterm ey + (48.0, 0.0, 16.0, 16.0), (16.0, 16.0, 4.0, 16.0), (4.0, 16.0, 0.0, 12.0)] # sterms + +vb = evaluate(b,x) + +for (vi,vbi) in zip(v,vb) + @test vi == vbi +end + +vb = evaluate(b,xi) +@test vb == v + +∇b = Broadcasting(gradient)(b) +gvb = evaluate(∇b,x) +for (vi,vbi) in zip(g,gvb) + @test vi == vbi +end + +gvb = evaluate(∇b,xi) +@test gvb == g + +@test length(b) == 15 +@test get_order(b) == 3 +@test get_orders(b) == (3,3) + +xi = Point(2,3,5) +np = 5 +x = fill(xi,np) + +order = 1 +D = 3 +k, r, rotate_90 = D-1, order+1, D==2 +V = VectorValue{D,T} +G = gradient_type(V,xi) +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,PT; rotate_90) + +@test length(b) == 15 +@test get_order(b) == 2 +@test get_orders(b) == (2,2,2) + + +# 1D + +order = 0 +D = 1 +k, r, rotate_90 = D-1, order+1, D==2 +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,PT; rotate_90) +@test testvalue(typeof(b)) isa typeof(b) + +@test b isa CartProdPolyBasis{D,T,PT} + +end # module diff --git a/test/PolynomialsTests/ExteriorCalculusBasesTests.jl b/test/PolynomialsTests/ExteriorCalculusBasesTests.jl new file mode 100644 index 000000000..20d8c2e4c --- /dev/null +++ b/test/PolynomialsTests/ExteriorCalculusBasesTests.jl @@ -0,0 +1,243 @@ +module ExteriorCalculusBasesTests + +using Base: diff_names +using Test +using Gridap.TensorValues +using Gridap.Fields +using Gridap.Arrays +using Gridap.Polynomials +using Gridap.Polynomials: _p_filter, _q_filter, _ser_filter +using ForwardDiff +using StaticArrays + +@test Polynomials._default_poly_type(:P⁻) == Bernstein +@test Polynomials._default_poly_type(:P) == Bernstein +@test Polynomials._default_poly_type(:Q⁻) == Polynomials.ModalC0 +@test Polynomials._default_poly_type(:S) == Polynomials.ModalC0 +@test Polynomials._default_poly_type(:default) == Monomial + +# differential geometry / exterior calculus isn't implemented yet +DG_calc = true +@test_throws ErrorException FEEC_space_definition_checks(Val(0),Float64,0,0,:P,false,DG_calc) + +# no vector proxy for 1 < k < D-1 forms +D,k = 4, 2 +@test_throws ErrorException FEEC_space_definition_checks(Val(D),Float64,0,k,:P) + +# rotate_90 is for 2D 1-forms +rotate_90 = true +@test_warn "`rotate_90` kwarg" FEEC_space_definition_checks(Val(2),Float64,0,2,:P,rotate_90) +@test_warn "`rotate_90` kwarg" FEEC_space_definition_checks(Val(3),Float64,0,1,:P,rotate_90) + +# cart_prod only for 0/D forms +cart_prod = true +rotate_90 = false +@test_throws "Cartesian product" FEEC_space_definition_checks(Val(2),Float64,0,1,:P,rotate_90; cart_prod) +@test FEEC_space_definition_checks(Val(2),VectorValue{3,Float64},0,0,:P,rotate_90; cart_prod) + +# Source: https://www-users.cse.umn.edu/~arnold/femtable/background.html +FEEC_length(r,k,D,::Val{:P⁻}) = binomial(r+D,r+k)*binomial(r+k-1,k) +FEEC_length(r,k,D,::Val{:P }) = binomial(r+D,r+k)*binomial(r+k,k) +FEEC_length(r,k,D,::Val{:Q⁻}) = binomial(D,k) * r^k * (r+1)^(D-k) +FEEC_length(r,k,D,::Val{:S }) = sum( 2^(D-d)*binomial(D,d)*binomial(r-d+2k,d)*binomial(d,k) for d in k:D ) + +T = Float64 # scalar type +r = 3 # Polynomial order +no_hessian = true + +@noinline function _test_bases(b, b2, r,k,F,D, no_hessian=false; cart_prod=false) + ncomp = cart_prod ? num_indep_components(return_type(b)) : 1 + @test length(b) == FEEC_length(r,k,D,Val(F))*ncomp + @test b isa typeof(b2) + @test evaluate(b,x) == evaluate(b2,x) + @test evaluate(Broadcasting(∇)(b),x) == evaluate(Broadcasting(∇)(b2),x) + @test testvalue(typeof(b)) isa typeof(b) + if no_hessian + @test_throws ErrorException evaluate(Broadcasting(∇∇)(b),x) == evaluate(Broadcasting(∇∇)(b2),x) + else + @test evaluate(Broadcasting(∇∇)(b),x) == evaluate(Broadcasting(∇∇)(b2),x) + end +end + + +# 1D +x = [Point(0.),Point(1.),Point(.4)] + +D, k = 1, 0 +function _k0D_tests(D,k,r,T) + VC = VectorValue{2,T} + m1 = k==D ? 1 : 0 + filter = k==D ? _p_filter : _ser_filter + for Vi in (T, VC) + cart_prod = Vi <: MultiValue + + # Monomial + b = FEEC_poly_basis(Val(D),Vi,r,k,:P⁻,Monomial; cart_prod) + b2 = CartProdPolyBasis(Monomial,Val(D),Vi,r-m1,_p_filter) + _test_bases(b,b2,r,k,:P⁻,D; cart_prod) + + b = FEEC_poly_basis(Val(D),Vi,r,k,:P,Monomial; cart_prod) + b2 = CartProdPolyBasis(Monomial,Val(D),Vi,r,_p_filter) + _test_bases(b,b2,r,k,:P,D; cart_prod) + + b = FEEC_poly_basis(Val(D),Vi,r,k,:Q⁻,Monomial; cart_prod) + b2 = CartProdPolyBasis(Monomial,Val(D),Vi,r-m1,_q_filter) + _test_bases(b,b2,r,k,:Q⁻,D; cart_prod) + + b = FEEC_poly_basis(Val(D),Vi,r,k,:S,Monomial; cart_prod) + b2 = CartProdPolyBasis(Monomial,Val(D),Vi,r,filter) + _test_bases(b,b2,r,k,:S,D; cart_prod) + + # Bernstein not PmΛ + if cart_prod + b = FEEC_poly_basis(Val(D),Vi,r,k,:P⁻,Bernstein; cart_prod) + b2 = BernsteinBasisOnSimplex{D}(Vi,r-m1) + _test_bases(b,b2,r,k,:P⁻,D; cart_prod) + + b = FEEC_poly_basis(Val(D),Vi,r,k,:P,Bernstein; cart_prod) + b2 = BernsteinBasisOnSimplex{D}(Vi,r) + _test_bases(b,b2,r,k,:P,D; cart_prod) + + b = FEEC_poly_basis(Val(D),Vi,r,k,:Q⁻,Bernstein; cart_prod) + b2 = CartProdPolyBasis(Bernstein,Val(D),Vi,r-m1,_q_filter) + _test_bases(b,b2,r,k,:Q⁻,D; cart_prod) + end + + if k==0 + @test_throws "hierarchical" FEEC_poly_basis(Val(D),Vi,r,k,:S,Bernstein; cart_prod) + else + b = FEEC_poly_basis(Val(D),Vi,r,k,:S,Bernstein; cart_prod) + b2 = BernsteinBasisOnSimplex{D}(Vi,r) + _test_bases(b,b2,r,k,:S,D; cart_prod) + end + end +end +_k0D_tests(D,k,r,T) + +D, k = 1, 1 +_k0D_tests(D,k,r,T) + + +# 2D +x = [Point(0.,0.),Point(1.,0),Point(0,.4),Point(.6,.4)] + +D, k = 2, 0 +_k0D_tests(D,k,r,T) + + +D, k = 2, 1 +V = VectorValue{D,T} + +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,Monomial) +b2 = NedelecPolyBasisOnSimplex{D}(Monomial,T,r-1) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P⁻,D,no_hessian) + +b = FEEC_poly_basis(Val(D),T,r,k,:P,Monomial) +b2 = CartProdPolyBasis(Monomial,Val(D),V,r,_p_filter) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P,D) + +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,Monomial) +orders = [ r-1 + (i==j ? 0 : 1) for i in 1:D, j in 1:D ] +b2 = CompWiseTensorPolyBasis{D}(Monomial, V, orders) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:Q⁻,D) + +@test_throws ErrorException FEEC_poly_basis(Val(D),T,r,k,:S,Monomial) +# b = FEEC_poly_basis(Val(D),T,r,k,:S,Monomial) +# b2 = # TODO +# @test b isa PolynomialBasis{D,V,r,Monomial} +#_test_bases(b,b3,r,k,:S,D) + +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,Monomial; rotate_90=true) +b2 = RaviartThomasPolyBasis{D}(Monomial,T,r-1) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P⁻,D,no_hessian) + +b = FEEC_poly_basis(Val(D),T,r,k,:P,Monomial; rotate_90=true) +b2 = CartProdPolyBasis(Monomial,Val(D),V,r,_p_filter) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P,D) + +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,Monomial; rotate_90=true) +orders = [ r-1 + (i==j ? 1 : 0) for i in 1:D, j in 1:D ] +b2 = CompWiseTensorPolyBasis{D}(Monomial,V,orders) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:Q⁻,D) + +@test_throws ErrorException FEEC_poly_basis(Val(D),T,r,k,:S,Monomial; rotate_90=true) +# b = FEEC_poly_basis(Val(D),T,r,k,:S,Monomial) +# b2 = # TODO +# @test b isa PolynomialBasis{D,V,r,Monomial} +#_test_bases(b,b3,r,k,:S,D) + + +D, k = 2, 2 +_k0D_tests(D,k,r,T) + + +# 3D +V = T +x = [Point(0.,0,0),Point(1.,0,0),Point(0,.4,.3),Point(.6,.4,.5)] + +D, k = 3, 0 +_k0D_tests(D,k,r,T) + +D, k = 3, 1 +V = VectorValue{D,T} +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,Monomial) +b2 = NedelecPolyBasisOnSimplex{D}(Monomial,T,r-1) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P⁻,D,no_hessian) + +b = FEEC_poly_basis(Val(D),T,r,k,:P,Monomial) +b2 = MonomialBasis(Val(D),V,r,Polynomials._p_filter) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P,D) + +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,Monomial) +orders = [ r-1 + (i==j ? 0 : 1) for i in 1:D, j in 1:D ] +b2 = CompWiseTensorPolyBasis{D}(Monomial, V, orders) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:Q⁻,D) + +@test_throws ErrorException FEEC_poly_basis(Val(D),T,r,k,:S,Monomial) +# b = FEEC_poly_basis(Val(D),T,r,k,:P,Monomial) +# b2 = +# @test b isa PolynomialBasis{D,V,r,Monomial} +# _test_bases(b,b2,r,k,:S,D) + + +D, k = 3, 2 +V = VectorValue{D,T} +b = FEEC_poly_basis(Val(D),T,r,k,:P⁻,Monomial) +b2 = RaviartThomasPolyBasis{D}(Monomial,T,r-1) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P⁻,D,no_hessian) + +b = FEEC_poly_basis(Val(D),T,r,k,:P,Monomial) +b2 = MonomialBasis(Val(D),V,r,Polynomials._p_filter) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:P,D) + +b = FEEC_poly_basis(Val(D),T,r,k,:Q⁻,Monomial) +orders = [ r-1 + (i==j ? 1 : 0) for i in 1:D, j in 1:D ] +b2 = CompWiseTensorPolyBasis{D}(Monomial, V, orders) +@test b isa PolynomialBasis{D,V,Monomial} +_test_bases(b,b2,r,k,:Q⁻,D) + +@test_throws ErrorException FEEC_poly_basis(Val(D),T,r,k,:S,Monomial) +# b = FEEC_poly_basis(Val(D),T,r,k,:P,Monomial) +# b2 = +# @test b isa PolynomialBasis{D,V,r,Monomial} +# _test_bases(b,b2,r,k,:S,D) + + +D, k = 3, 3 +_k0D_tests(D,k,r,T) + +@test_throws ErrorException FEEC_poly_basis(Val(4),T,r,1,:P⁻,Monomial) +@test_throws ErrorException FEEC_poly_basis(Val(4),T,r,2,:P⁻,Monomial) + +end # module diff --git a/test/PolynomialsTests/ForwardDiffTests.jl b/test/PolynomialsTests/ForwardDiffTests.jl index ca08aca2e..7fa5077ef 100644 --- a/test/PolynomialsTests/ForwardDiffTests.jl +++ b/test/PolynomialsTests/ForwardDiffTests.jl @@ -8,17 +8,17 @@ using Gridap.Fields using Gridap.Polynomials dualise(x::Number,N) = ForwardDiff.Dual(x,ForwardDiff.Partials(ntuple(i -> 0.0, N))) -dualise(x::Point,N) = Point(dualise.(x.data,N)) +dualise(x::Point,N) = Point(dualise.(x.data,N)) x = [Point(0.,0.),Point(1.,0.)] xd = dualise.(x,2) order = 1 V = Float64 -b = MonomialBasis{2}(V,order) +b = MonomialBasis(Val(2),V,order) evaluate(b,xd) g = Broadcasting(∇)(b) evaluate(g,xd) -end # module \ No newline at end of file +end # module diff --git a/test/PolynomialsTests/JacobiPolynomialBasesTests.jl b/test/PolynomialsTests/LegendreBasesTests.jl similarity index 83% rename from test/PolynomialsTests/JacobiPolynomialBasesTests.jl rename to test/PolynomialsTests/LegendreBasesTests.jl index fdbfe87fc..09af8c628 100644 --- a/test/PolynomialsTests/JacobiPolynomialBasesTests.jl +++ b/test/PolynomialsTests/LegendreBasesTests.jl @@ -1,11 +1,14 @@ -module JacobiPolynomialBasisTests +module LegendreBasisTests using Test +using Gridap.Arrays using Gridap.TensorValues using Gridap.Fields using Gridap.Fields: Broadcasting using Gridap.Polynomials +@test isHierarchical(Legendre) == true + # Real-valued Q space with isotropic order x1 = Point(0.0) @@ -17,24 +20,25 @@ G = gradient_type(V,x1) H = gradient_type(G,x1) order = 3 -b1 = JacobiPolynomialBasis{1}(V,order) +b1 = LegendreBasis(Val(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,); +@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,); +@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 = LegendreBasis(Val(2),V,order) +@test testvalue(typeof(b2)) isa typeof(b2) ∇b2 = Broadcasting(∇)(b2) ∇∇b2 = Broadcasting(∇)(∇b2) @@ -45,18 +49,18 @@ H = gradient_type(G,x1) =# -1.7320508075688772 2.9999999999999996 -3.872983346207417 #= =# 4.58257569495584 2.23606797749979 -3.872983346207417 #= =# 5.000000000000001 -5.916079783099617 -2.6457513110645907 #= - =# 4.58257569495584 -5.916079783099617 7.000000000000001; + =# 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.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 #= + =# 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); +@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); +@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) ] diff --git a/test/PolynomialsTests/ModalC0BasesTests.jl b/test/PolynomialsTests/ModalC0BasesTests.jl index f5e084633..60b7c95a4 100644 --- a/test/PolynomialsTests/ModalC0BasesTests.jl +++ b/test/PolynomialsTests/ModalC0BasesTests.jl @@ -1,9 +1,11 @@ module ModalC0BasesTests using Test +using Gridap.Arrays using Gridap.TensorValues using Gridap.Fields using Gridap.Polynomials +using StaticArrays # using BenchmarkTools import Gridap.Fields: Broadcasting @@ -22,6 +24,9 @@ order = 3 a = fill(Point(-0.5),order+1) b = fill(Point(2.5),order+1) b1 = ModalC0Basis{1}(V,order,a,b) + +@test IndexStyle(b1) == IndexLinear() + ∇b1 = Broadcasting(∇)(b1) ∇∇b1 = Broadcasting(∇)(∇b1) @@ -35,6 +40,25 @@ b1 = ModalC0Basis{1}(V,order,a,b) (0.0,) (0.0,) (3.4641016151377544,) (-1.4907119849998598,); (0.0,) (0.0,) (3.4641016151377544,) (2.9814239699997196,)] +# Validate generic 1D implem using CartProdPolyBasis + +order = 3 +a = fill(Point(0.),order+1) +b = fill(Point(1.),order+1) +b1 = ModalC0Basis{1}(V,order,a,b) +@test testvalue(typeof(b1)) isa typeof(b1) +b1u= CartProdPolyBasis(ModalC0,Val(1),V,order) + +∇b1 = Broadcasting(∇)(b1) +∇b1u = Broadcasting(∇)(b1u) +∇∇b1 = Broadcasting(∇)(∇b1) +∇∇b1u= Broadcasting(∇)(∇b1u) + +@test evaluate(b1, [x1,x2,x3,]) ≈ evaluate(b1u, [x1,x2,x3,]) +@test evaluate(∇b1, [x1,x2,x3,]) ≈ evaluate(∇b1u, [x1,x2,x3,]) +@test evaluate(∇∇b1,[x1,x2,x3,]) ≈ evaluate(∇∇b1u,[x1,x2,x3,]) + + x1 = Point(0.0,0.0) x2 = Point(0.5,0.5) x3 = Point(1.0,1.0) @@ -61,4 +85,47 @@ H = gradient_type(G,x1) (0.0, 0.5590169943749475, 0.5590169943749475, 1.118033988749895); (0.0, -2.23606797749979, -2.23606797749979, 0.0) ] -end # module \ No newline at end of file +# Validate generic 2D implem using CartProdPolyBasis + +order = 3 +len_b2 = (order+1)^2 +a = fill(Point(0.,0.), len_b2) +b = fill(Point(1.,1.), len_b2) + +b2 = ModalC0Basis{2}(V,order,a,b) +b2u= CartProdPolyBasis(ModalC0,Val(2),V,order) +∇b2 = Broadcasting(∇)(b2) +∇b2u = Broadcasting(∇)(b2u) + +b2x = collect(eachcol(evaluate(b2, [x1,x2,x3,]))) +b2xu = collect(eachcol(evaluate(b2u, [x1,x2,x3,]))) +∇b2x = collect(eachcol(evaluate(∇b2, [x1,x2,x3,]))) +∇b2xu = collect(eachcol(evaluate(∇b2u, [x1,x2,x3,]))) + +# re order basis polynomials as each basis has different ordering ... +b2x_perm = b2x[ sortperm(b2x)[ invperm(sortperm(b2xu))]] +∇b2x_perm = ∇b2x[ sortperm(∇b2x)[ invperm(sortperm(∇b2xu))]] + +@test b2xu == b2x_perm +@test ∇b2xu == ∇b2x_perm + + +# Misc + +# Derivatives not implemented for symetric tensor types + +D = 2 +T = Float64 +V = SymTensorValue{D,T} +G = gradient_type(V,x1) +s = MVector(0.,0.) +r = zeros(G, (1,1)) +@test_throws ErrorException Polynomials._set_derivative_mc0!(r,1,s,0,0,V) + +V = SymTracelessTensorValue{D,T} +G = gradient_type(V,x1) +r = zeros(G, (1,1)) +@test_throws ErrorException Polynomials._set_derivative_mc0!(r,1,s,0,0,V) + + +end # module diff --git a/test/PolynomialsTests/MonomialBasesTests.jl b/test/PolynomialsTests/MonomialBasesTests.jl index fc92907e7..1edd4f042 100644 --- a/test/PolynomialsTests/MonomialBasesTests.jl +++ b/test/PolynomialsTests/MonomialBasesTests.jl @@ -6,6 +6,10 @@ using Gridap.Fields using Gridap.Polynomials using Gridap.Arrays: testvalue +using Gridap.Polynomials: _q_filter, _qh_filter, _p_filter, _ph_filter + +@test isHierarchical(Monomial) == true + xi = Point(2,3) np = 5 x = fill(xi,np) @@ -16,7 +20,7 @@ order = 0 V = Float64 G = gradient_type(V,xi) H = gradient_type(G,xi) -b = MonomialBasis{2}(V,order) +b = MonomialBasis(Val(2),V,order) @test testvalue(typeof(b)) isa typeof(b) @test get_order(b) == 0 @test get_orders(b) == (0,0) @@ -37,7 +41,7 @@ order = 1 V = Float64 G = gradient_type(V,xi) H = gradient_type(G,xi) -b = MonomialBasis{2}(V,order) +b = MonomialBasis(Val(2),V,order) v = V[1.0, 2.0, 3.0, 6.0] g = G[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (3.0, 2.0)] @@ -51,18 +55,21 @@ test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:],gradgrad=Hbx[1,:]) # Real-valued Q space with an isotropic order -orders = (1,2) +orders = (1,3) V = Float64 G = gradient_type(V,xi) -b = MonomialBasis{2}(V,orders) +H = gradient_type(G,xi) +b = MonomialBasis(Val(2),V,orders) -v = V[1.0, 2.0, 3.0, 6.0, 9.0, 18.0] -g = G[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (3.0, 2.0), (0.0, 6.0), (9.0, 12.0)] +v = V[1.0, 2.0, 3.0, 6.0, 9.0, 18.0, 27.0, 54.0] +g = G[(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (3.0, 2.0), (0.0, 6.0), (9.0, 12.0), (0., 27.0), (27.0, 54.0)] +h = H[(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, 1.0, 1.0, 0.0), (0.0, 0.0, 0.0, 2.0), (0.0, 6.0, 6.0, 4.0), (0.0, 0.0, 0.0, 18.0), (0.0, 27.0, 27.0, 36.0)] bx = repeat(permutedims(v),np) ∇bx = repeat(permutedims(g),np) -test_field_array(b,x,bx,grad=∇bx) -test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) +Hbx = repeat(permutedims(h),np) +test_field_array(b,x,bx,grad=∇bx,gradgrad=Hbx) +test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:],gradgrad=Hbx[1,:]) # Vector-valued Q space with isotropic order @@ -70,7 +77,7 @@ order = 1 V = VectorValue{3,Float64} G = gradient_type(V,xi) H = gradient_type(G,xi) -b = MonomialBasis{2}(V,order) +b = MonomialBasis(Val(2),V,order) v = V[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 2.0], @@ -109,7 +116,7 @@ test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:],gradgrad=Hbx[1,:]) orders = (1,2) V = VectorValue{2,Float64} G = gradient_type(V,xi) -b = MonomialBasis{2}(V,orders) +b = MonomialBasis(Val(2),V,orders) v = V[ (1.0, 0.0), (0.0, 1.0), (2.0, 0.0), (0.0, 2.0), @@ -135,7 +142,7 @@ order = 1 V = Float64 G = gradient_type(V,xi) filter = (e,o) -> sum(e) <= o -b = MonomialBasis{2}(V,order,filter) +b = MonomialBasis(Val(2),V,order,filter) v = V[1.0, 2.0, 3.0] g = G[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]] @@ -151,7 +158,7 @@ order = 1 V = VectorValue{3,Float64} G = gradient_type(V,xi) filter = (e,o) -> sum(e) <= o -b = MonomialBasis{2}(V,order,filter) +b = MonomialBasis(Val(2),V,order,filter) v = V[[1.0; 0.0; 0.0], [0.0; 1.0; 0.0], [0.0; 0.0; 1.0], [2.0; 0.0; 0.0], [0.0; 2.0; 0.0], [0.0; 0.0; 2.0], @@ -174,7 +181,7 @@ order = 1 V = SymTensorValue{2,Float64} G = gradient_type(V,xi) filter = (e,o) -> sum(e) <= o -b = MonomialBasis{2}(V,order,filter) +b = MonomialBasis(Val(2),V,order,filter) v = V[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (2.0, 0.0, 0.0), (0.0, 2.0, 0.0), (0.0, 0.0, 2.0), @@ -201,7 +208,7 @@ order = 1 V = SymTracelessTensorValue{2,Float64} G = gradient_type(V,xi) filter = (e,o) -> sum(e) <= o -b = MonomialBasis{2}(V,order,filter) +b = MonomialBasis(Val(2),V,order,filter) v = V[(1.0, 0.0), (0.0, 1.0), (2.0, 0.0), (0.0, 2.0), @@ -221,27 +228,54 @@ test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) order = 1 -b = MonomialBasis{1}(Float64,order) +b = MonomialBasis(Val(1),Float64,order) @test evaluate(b,Point{1,Float64}[(0,),(1,)]) == [1.0 0.0; 1.0 1.0] -b = MonomialBasis{0}(VectorValue{2,Float64},order) +b = MonomialBasis(Val(0),VectorValue{2,Float64},order) @test evaluate(b,Point{0,Float64}[(),()]) == VectorValue{2,Float64}[(1.0, 0.0) (0.0, 1.0); (1.0, 0.0) (0.0, 1.0)] -b = MonomialBasis{0}(TensorValue{2,2,Float64},order) +b = MonomialBasis(Val(0),TensorValue{2,2,Float64},order) @test evaluate(b,Point{0,Float64}[(),()]) == TensorValue{2,2,Float64}[ (1.0, 0.0, 0.0, 0.0) (0.0, 1.0, 0.0, 0.0) (0.0, 0.0, 1.0, 0.0) (0.0, 0.0, 0.0, 1.0); (1.0, 0.0, 0.0, 0.0) (0.0, 1.0, 0.0, 0.0) (0.0, 0.0, 1.0, 0.0) (0.0, 0.0, 0.0, 1.0) ] -b = MonomialBasis{0}(SymTensorValue{2,Float64},order) +b = MonomialBasis(Val(0),SymTensorValue{2,Float64},order) @test evaluate(b,Point{0,Float64}[(),()]) == SymTensorValue{2,Float64}[ (1.0, 0.0, 0.0) (0.0, 1.0, 0.0) (0.0, 0.0, 1.0); (1.0, 0.0, 0.0) (0.0, 1.0, 0.0) (0.0, 0.0, 1.0) ] -b = MonomialBasis{0}(SymTracelessTensorValue{2,Float64},order) +b = MonomialBasis(Val(0),SymTracelessTensorValue{2,Float64},order) @test evaluate(b,Point{0,Float64}[(),()]) == SymTracelessTensorValue{2,Float64}[ (1.0, 0.0) (0.0, 1.0); (1.0, 0.0) (0.0, 1.0) ] +order = 2 + +@test _q_filter( (1,2) ,order) == true +@test _q_filter( (2,0) ,order) == true +@test _q_filter( (2,2) ,order) == true +@test _q_filter( (1,1) ,order) == true +@test _q_filter( (3,1) ,order) == false + +@test _qh_filter( (1,2) ,order) == true +@test _qh_filter( (2,0) ,order) == true +@test _qh_filter( (2,2) ,order) == true +@test _qh_filter( (1,1) ,order) == false +@test _qh_filter( (3,1) ,order) == false + +@test _p_filter( (1,2) ,order) == false +@test _p_filter( (2,0) ,order) == true +@test _p_filter( (2,2) ,order) == false +@test _p_filter( (1,1) ,order) == true +@test _p_filter( (3,1) ,order) == false +@test _p_filter( (0,1) ,order) == true + +@test _ph_filter( (1,2) ,order) == false +@test _ph_filter( (2,0) ,order) == true +@test _ph_filter( (2,2) ,order) == false +@test _ph_filter( (1,1) ,order) == true +@test _ph_filter( (3,1) ,order) == false + end # module diff --git a/test/PolynomialsTests/PCurlGradMonomialBasesTests.jl b/test/PolynomialsTests/PCurlGradMonomialBasesTests.jl deleted file mode 100644 index f11e9f6c7..000000000 --- a/test/PolynomialsTests/PCurlGradMonomialBasesTests.jl +++ /dev/null @@ -1,67 +0,0 @@ -module PCurlGradMonomialBasesTests - -using Test -using Gridap.TensorValues -using Gridap.Fields -using Gridap.Polynomials -using Gridap.Arrays - -xi = Point(4,2) -np = 1 -x = fill(xi,np) - -order = 2 -D = 2 -T = Float64 -V = VectorValue{D,T} -G = gradient_type(V,xi) -b = PCurlGradMonomialBasis{D}(T,order) - -v = V[ - (1.0, 0.0), (0.0, 1.0), (4.0, 0.0), (0.0, 2.0), (16.0, 0.0), (0.0, 4.0), - (2.0, 0.0), (0.0, 4.0), (8.0, 0.0), (0.0, 8.0), (4.0, 0.0), (0.0, 16.0), - (64.0, 32.0), (32.0, 16.0), (16.0, 8.0)] - -g = G[ - (0.0, 0.0, 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, 1.0), (8.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 4.0), - (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (2.0, 4.0, 0.0, 0.0), - (0.0, 0.0, 2.0, 4.0), (0.0, 4.0, 0.0, 0.0), (0.0, 0.0, 8.0, 0.0), - (48.0, 0.0, 16.0, 16.0), (16.0, 16.0, 4.0, 16.0), (4.0, 16.0, 0.0, 12.0)] - - vb = evaluate(b,x) - - for (vi,vbi) in zip(v,vb) - @test vi == vbi - end - - vb = evaluate(b,xi) - @test vb == v - - ∇b = Broadcasting(gradient)(b) - gvb = evaluate(∇b,x) - for (vi,vbi) in zip(g,gvb) - @test vi == vbi - end - - gvb = evaluate(∇b,xi) - @test gvb == g - - @test num_terms(b) == 15 - @test get_order(b) == 2 - - xi = Point(2,3,5) - np = 5 - x = fill(xi,np) - - order = 1 - D = 3 - T = Float64 - V = VectorValue{D,T} - G = gradient_type(V,xi) - b = PCurlGradMonomialBasis{D}(T,order) - - @test num_terms(b) == 15 - @test get_order(b) == 1 - -end # module diff --git a/test/PolynomialsTests/PolynomialInterfacesTests.jl b/test/PolynomialsTests/PolynomialInterfacesTests.jl new file mode 100644 index 000000000..e692f1ad7 --- /dev/null +++ b/test/PolynomialsTests/PolynomialInterfacesTests.jl @@ -0,0 +1,80 @@ +module PolynomialInterfacesTests + +using Test +using Gridap.Arrays +using Gridap.Fields +using Gridap.Polynomials +using StaticArrays + +xi = Point(0.) +np = 5 +x = fill(xi,np) + +###################### +# Polynomial interface +###################### + +struct MockPolynomial <: Polynomial end +@test_throws ErrorException isHierarchical(Polynomial) +@test_throws ErrorException testvalue(Polynomial) + +# Interfaces to implement +@test_throws ErrorException isHierarchical(MockPolynomial) + +D = 1 +K = 0 +c = zero(MMatrix{D,K+1}) + +@test_throws ErrorException Polynomials._evaluate_1d!(MockPolynomial, 1, c, xi, 1) +@test_throws ErrorException Polynomials._gradient_1d!(MockPolynomial, 1, c, xi, 1) +@test_throws ErrorException Polynomials._hessian_1d!( MockPolynomial, 1, c, xi, 1) +@test_throws ErrorException Polynomials._derivatives_1d!(MockPolynomial, 1, (nothing,nothing,nothing,nothing), xi, 1) + +function Polynomials._evaluate_1d!( + ::Type{MockPolynomial},K::Int, cc::AbstractMatrix{T}, xi, d) where T<:Number + + cc[1,1] = 1. +end +Polynomials._derivatives_1d!(MockPolynomial, 1, (c,), xi, 1) +@test c[1][1] == 1. + +########################### +# PolynomialBasis interface +########################### + +T = Float64 +D = 1 +struct MockPolyBasis <: PolynomialBasis{D,T,MockPolynomial} end + +mb = MockPolyBasis() + +# Implemented interfaces +@test IndexStyle(mb) == IndexLinear() +@test return_type(mb) == T +@test mb[1] == MockPolynomial() +@test_throws ErrorException get_order(mb) +@test_throws ErrorException get_orders(mb) +@test_throws ErrorException testvalue(mb) + +Polynomials.get_order(b::MockPolyBasis) = 0 + +# Interfaces to implement +@test_throws ErrorException size(mb) +import Base.size +Base.size(::MockPolyBasis) = (1,) +@test length(mb) == 1 + + +r, _, c = return_cache(mb,x) +@test_throws ErrorException Polynomials._evaluate_nd!(mb, xi, r, 1, c, nothing) + +∇mb = FieldGradientArray{1}(mb) +r, s, c, g = return_cache(∇mb,x) +@test_throws ErrorException Polynomials._gradient_nd!(mb, xi, r, 1, c, g, s, nothing) + +Hmb = FieldGradientArray{2}(mb) +r, s, c, g, h = return_cache(Hmb,x) +@test_throws ErrorException Polynomials._hessian_nd!(mb, xi, r, 1, c, g, h, s, nothing) + + +end diff --git a/test/PolynomialsTests/QCurlGradMonomialBasesTests.jl b/test/PolynomialsTests/QCurlGradMonomialBasesTests.jl deleted file mode 100644 index 83d98c270..000000000 --- a/test/PolynomialsTests/QCurlGradMonomialBasesTests.jl +++ /dev/null @@ -1,80 +0,0 @@ -module QCurlGradMonomialBasesTests - -using Test -using Gridap.TensorValues -using Gridap.Fields -using Gridap.Polynomials - -xi = Point(2,3) -np = 5 -x = fill(xi,np) - -order = 0 -D = 2 -T = Float64 -V = VectorValue{D,T} -G = gradient_type(V,xi) -b = QCurlGradMonomialBasis{D}(T,order) - -@test num_terms(b) == 4 -@test get_order(b) == 0 -@test return_type(b) == V - -xi = Point(2,3,5) -np = 5 -x = fill(xi,np) - -order = 0 -D = 3 -T = Float64 -V = VectorValue{D,T} -G = gradient_type(V,xi) -b = QCurlGradMonomialBasis{D}(T,order) - -v = V[ - (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), - (2.0, 0.0, 0.0), (0.0, 3.0, 0.0), (0.0, 0.0, 5.0)] - -g = G[ - (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, 0.0, 0.0), - (0.0, 0.0, 0.0, 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, 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, 1.0)] - -bx = repeat(permutedims(v),np) -∇bx = repeat(permutedims(g),np) -test_field_array(b,x,bx,grad=∇bx) -test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) - -xi = Point(2,3) -np = 5 -x = fill(xi,np) - -order = 1 -D = 2 -T = Float64 -V = VectorValue{D,T} -G = gradient_type(V,xi) -b = QCurlGradMonomialBasis{D}(T,order) - -v = V[ - (1.0, 0.0), (0.0, 1.0), (2.0, 0.0), (0.0, 3.0), - (4.0, 0.0), (0.0, 9.0), (3.0, 0.0), (0.0, 2.0), - (6.0, 0.0), (0.0, 6.0), (12.0, 0.0), (0.0, 18.0)] - -g = G[ - (0.0, 0.0, 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, 1.0), - (4.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 6.0), - (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), - (3.0, 2.0, 0.0, 0.0), (0.0, 0.0, 3.0, 2.0), - (12.0, 4.0, 0.0, 0.0),(0.0, 0.0, 9.0, 12.0)] - -bx = repeat(permutedims(v),np) -∇bx = repeat(permutedims(g),np) -test_field_array(b,x,bx,grad=∇bx) -test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) - -end # module diff --git a/test/PolynomialsTests/QGradMonomialBasesTests.jl b/test/PolynomialsTests/QGradMonomialBasesTests.jl deleted file mode 100644 index 00e47503b..000000000 --- a/test/PolynomialsTests/QGradMonomialBasesTests.jl +++ /dev/null @@ -1,127 +0,0 @@ -module QGradMonomialBasesTests - -using Test -using Gridap.TensorValues -using Gridap.Fields -using Gridap.Arrays -using Gridap.Polynomials - -xi = Point(2,3) -np = 5 -x = fill(xi,np) - -order = 0 -D = 2 -T = Float64 -V = VectorValue{D,T} -G = gradient_type(V,xi) -b = QGradMonomialBasis{D}(T,order) - -@test num_terms(b) == 4 -@test b.order == 0 -@test get_order(b) == 0 -@test return_type(b) == V - -xi = Point(2,3,5) -np = 5 -x = fill(xi,np) - -order = 0 -D = 3 -T = Float64 -V = VectorValue{D,T} -G = gradient_type(V,xi) -b = QGradMonomialBasis{D}(T,order) - -v = V[ - (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), - (3.0, 0.0, 0.0), (0.0, 5.0, 0.0), (0.0, 0.0, 2.0), - (5.0, 0.0, 0.0), (0.0, 2.0, 0.0), (0.0, 0.0, 3.0), - (15.0, 0.0, 0.0), (0.0, 10.0, 0.0), (0.0, 0.0, 6.0)] - -g = G[ - (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, 0.0, 0.0), - (0.0, 0.0, 0.0, 0.0, 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, 1.0, 0.0, 0.0, 0.0), - (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.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, 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, 1.0, 0.0), - (0.0, 5.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), - (0.0, 0.0, 0.0, 5.0, 0.0, 2.0, 0.0, 0.0, 0.0), - (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 2.0, 0.0)] - -bx = repeat(permutedims(v),np) -∇bx = repeat(permutedims(g),np) -test_field_array(b,x,bx,grad=∇bx) -test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) - -xi = Point(2,3) -np = 5 -x = fill(xi,np) - -order = 1 -D = 2 -T = Float64 -V = VectorValue{D,T} -G = gradient_type(V,xi) -b = QGradMonomialBasis{D}(T,order) - -v = V[ - (1.0, 0.0), (0.0, 1.0), (2.0, 0.0), (0.0, 3.0), - (3.0, 0.0), (0.0, 2.0), (6.0, 0.0), (0.0, 6.0), - (9.0, 0.0), (0.0, 4.0), (18.0, 0.0), (0.0, 12.0)] - -g = G[ - (0.0, 0.0, 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, 1.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), - (3.0, 2.0, 0.0, 0.0), (0.0, 0.0, 3.0, 2.0), (0.0, 6.0, 0.0, 0.0), - (0.0, 0.0, 4.0, 0.0), (9.0, 12.0, 0.0, 0.0), (0.0, 0.0, 12.0, 4.0)] - -bx = repeat(permutedims(v),np) -∇bx = repeat(permutedims(g),np) -test_field_array(b,x,bx,grad=∇bx) -test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) - -xi = Point(2.,3.,5.) -np = 3 -x = fill(xi,np) - -order = 0 -D = 3 -b = Polynomials.NedelecPrebasisOnSimplex{D}(order) - -V = VectorValue{D, Float64} -v = V[(1,0,0),(0,1,0),(0,0,1),(-3,2,0),(-5,0,2),(0,-5,3)] -@test return_type(b) == V - -G = gradient_type(V,xi) -g = G[ - (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,-1,0, 1,0,0, 0,0,0),(0,0,-1, 0,0,0, 1,0,0),(0,0,0, 0,0,-1, 0,1,0)] - -bx = repeat(permutedims(v),np) -∇bx = repeat(permutedims(g),np) -test_field_array(b,x,bx,grad=∇bx) -test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) - -xi = Point(2.,3.) -np = 4 -x = fill(xi,np) - -order = 0 -D = 2 -b = Polynomials.NedelecPrebasisOnSimplex{D}(order) -V = VectorValue{D, Float64} -v = V[(1,0),(0,1),(-3,2)] -G = gradient_type(V,xi) -g = G[(0,0, 0,0), (0,0, 0,0), (0,-1, 1,0)] -bx = repeat(permutedims(v),np) -∇bx = repeat(permutedims(g),np) -test_field_array(b,x,bx,grad=∇bx) -test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:]) - - -end # module diff --git a/test/PolynomialsTests/runtests.jl b/test/PolynomialsTests/runtests.jl index cefc2990c..69711beeb 100644 --- a/test/PolynomialsTests/runtests.jl +++ b/test/PolynomialsTests/runtests.jl @@ -2,17 +2,27 @@ module PolynomialsTests using Test +@testset "PolynomialInterfaces" begin include("PolynomialInterfacesTests.jl") end + @testset "MonomialBases" begin include("MonomialBasesTests.jl") end -@testset "QGradMonomialBases" begin include("QGradMonomialBasesTests.jl") end +@testset "CompWiseTensorPolyBases" begin include("CompWiseTensorPolyBasesTests.jl") end -@testset "QCurlGradMonomialBases" begin include("QCurlGradMonomialBasesTests.jl") end +@testset "CurlConformBases" begin include("CurlConformBasesTests.jl") end -@testset "PCurlGradMonomialBases" begin include("PCurlGradMonomialBasesTests.jl") end +@testset "DivConformBases" begin include("DivConformBasesTests.jl") end @testset "ModalC0Bases" begin include("ModalC0BasesTests.jl") end -@testset "JacobiPolynomialBases" begin include("JacobiPolynomialBasesTests.jl") end +@testset "LegendreBases" begin include("LegendreBasesTests.jl") end + +@testset "ChebyshevBases" begin include("ChebyshevBasesTests.jl") end + +@testset "BernsteinBases" begin include("BernsteinBasesTests.jl") end + +@testset "BarycentricPΛBases" begin include("BarycentricPΛBases.jl") end + +@testset "FEECBases" begin include("ExteriorCalculusBasesTests.jl") end @testset "ForwardDiffTests.jl" begin include("ForwardDiffTests.jl") end diff --git a/test/ReferenceFEsTests/BDMRefFEsTests.jl b/test/ReferenceFEsTests/BDMRefFEsTests.jl index f695d2c19..bb62e0ea6 100644 --- a/test/ReferenceFEsTests/BDMRefFEsTests.jl +++ b/test/ReferenceFEsTests/BDMRefFEsTests.jl @@ -40,6 +40,30 @@ cache = return_cache(dof_basis,prebasis) r = evaluate!(cache, dof_basis, prebasis) test_dof_array(dof_basis,prebasis,r) +order = 3 + +reffe = BDMRefFE(et,p,order) +@test_warn "falling back to `change_dof=false`" BDMRefFE(et,p,order; change_dof=true, poly_type=Monomial) + +@test length(get_prebasis(reffe)) == 20 +@test get_order(get_prebasis(reffe)) == 3 +@test num_dofs(reffe) == 20 +@test Conformity(reffe) == DivConformity() + +prebasis = get_prebasis(reffe) +dof_basis = get_dof_basis(reffe) + +v = VectorValue(3.0,0.0) +field = GenericField(x->v*x[1]) + +cache = return_cache(dof_basis,field) +r = evaluate!(cache, dof_basis, field) +test_dof_array(dof_basis,field,r) + +cache = return_cache(dof_basis,prebasis) +r = evaluate!(cache, dof_basis, prebasis) +test_dof_array(dof_basis,prebasis,r) + p = TET D = num_dims(TET) @@ -79,19 +103,54 @@ cache = return_cache(dof_basis,prebasis) r = evaluate!(cache, dof_basis, prebasis) test_dof_array(dof_basis,prebasis,r) +order = 3 + +reffe = BDMRefFE(et,p,order) +test_reference_fe(reffe) +@test length(get_prebasis(reffe)) == 60 +@test num_dofs(reffe) == 60 +@test get_order(get_prebasis(reffe)) == 3 +@test Conformity(reffe) == DivConformity() + +prebasis = get_prebasis(reffe) +dof_basis = get_dof_basis(reffe) + +v = VectorValue(0.0,3.0,0.0) +field = GenericField(x->v) + +cache = return_cache(dof_basis,field) +r = evaluate!(cache, dof_basis, field) +test_dof_array(dof_basis,field,r) + +cache = return_cache(dof_basis,prebasis) +r = evaluate!(cache, dof_basis, prebasis) +test_dof_array(dof_basis,prebasis,r) + + # Factory function reffe = ReferenceFE(TET,bdm,1) +@test reffe == ReferenceFE(TET,:P,1,2) # r=1, k=2 @test length(get_prebasis(reffe)) == 12 @test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 12 @test Conformity(reffe) == DivConformity() +@test_warn "falling back to `change_dof=false`" ReferenceFE(TET,bdm,1; poly_type=Monomial) + reffe = ReferenceFE(TET,bdm,Float64,1) +@test reffe == ReferenceFE(TET,:P,1,2,Float64) # r=1, k=2 @test length(get_prebasis(reffe)) == 12 @test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 12 @test Conformity(reffe) == DivConformity() +reffe = ReferenceFE(TRI,bdm,1) +@test reffe == ReferenceFE(TRI,:P,1,1; rotate_90=true) # r=1, k=2 + +# Serendipity BDM not implemented +@test_throws ErrorException ReferenceFE(QUAD,:S,1,1; rotate_90=true) +@test_throws ErrorException ReferenceFE(HEX, :S,1,2) + @test Conformity(reffe,:L2) == L2Conformity() @test Conformity(reffe,:Hdiv) == DivConformity() @test Conformity(reffe,:HDiv) == DivConformity() diff --git a/test/ReferenceFEsTests/BezierRefFEsTests.jl b/test/ReferenceFEsTests/BezierRefFEsTests.jl index 483cdfad8..ae17c465f 100644 --- a/test/ReferenceFEsTests/BezierRefFEsTests.jl +++ b/test/ReferenceFEsTests/BezierRefFEsTests.jl @@ -20,7 +20,7 @@ p_filter(e,o) = sum(e) ≤ o # 1D p = 2 -prebasis_seg = MonomialBasis{1}(Float64,p,p_filter) +prebasis_seg = MonomialBasis(Val(1),Float64,p,p_filter) C = _berstein_matrix(prebasis_seg,SEGMENT) C12 = [ @@ -41,7 +41,7 @@ Xi = lazy_map( evaluate, Ψ, ξ ) @test Xi == [ Point(0.0,0.0), Point(0.5,0.25), Point(1.0,0.0) ] p = 3 -prebasis_seg = MonomialBasis{1}(Float64,p,p_filter) +prebasis_seg = MonomialBasis(Val(1),Float64,p,p_filter) C = _berstein_matrix(prebasis_seg,SEGMENT) C13 = [ @@ -65,7 +65,7 @@ Xi = lazy_map( evaluate, Ψ, ξ ) # 2D p = 2 -prebasis_tri = MonomialBasis{2}(Float64,p,p_filter) +prebasis_tri = MonomialBasis(Val(2),Float64,p,p_filter) C = _berstein_matrix(prebasis_tri,TRI) C22 = [ @@ -89,7 +89,7 @@ Xi = lazy_map( evaluate, Ψ, ξ ) @test Xi == ξ p = 3 -prebasis_tri = MonomialBasis{2}(Float64,p,p_filter) +prebasis_tri = MonomialBasis(Val(2),Float64,p,p_filter) C = _berstein_matrix(prebasis_tri,TRI) C23 = [ @@ -123,6 +123,8 @@ Xi = lazy_map( evaluate, Ψ, ξ ) ## BezierRefFE +@test_throws ErrorException BezierRefFE(Float64,WEDGE,3) # only n-cubes & simplices + tri = BezierRefFE(Float64,TRI,(3,3)) nodes = get_node_coordinates(tri) * 5 ϕ = get_shapefuns(tri) diff --git a/test/ReferenceFEsTests/BubbleRefFEsTests.jl b/test/ReferenceFEsTests/BubbleRefFEsTests.jl index b862eca52..192f62db4 100644 --- a/test/ReferenceFEsTests/BubbleRefFEsTests.jl +++ b/test/ReferenceFEsTests/BubbleRefFEsTests.jl @@ -10,43 +10,46 @@ using Gridap.Arrays: evaluate # mini bubble tests et = Float64 +max_order = 2 + for p in [SEGMENT, TRI, QUAD, TET, HEX] - for T in [et, VectorValue{2, et}, VectorValue{3, et}] - reffe = BubbleRefFE(T, p) - test_reference_fe(reffe) - - N = num_components(T) - @test num_dofs(reffe) == N - @test Conformity(reffe) == L2Conformity() - @test get_polytope(reffe) == p - - face_dofs = fill(Int[], num_faces(p)) - face_dofs[end] = 1:N - @test get_face_dofs(reffe) == face_dofs - - prebasis = get_prebasis(reffe) - @test length(prebasis) == N - @test prebasis isa LinearCombinationFieldVector - - shapefuns = get_shapefuns(reffe) - @test length(shapefuns) == N - - dofs = get_dof_basis(reffe) - xs = get_face_coordinates(p) - bxs = map(mean, xs) - bx0 = bxs[end] - # dof is at the barycenter of the polytope - for dof in dofs - @test bx0 == dof.point - end - # equal to 1 at the barycenter - val = evaluate(dofs, shapefuns) - @test val == one(val) - - # equal to 0 at the barycenters of each d < D faces - vals = evaluate(shapefuns, bxs[1:(end-1)]) - @test all(vals .== zero(T)) - end + for T in [et, VectorValue{2, et}, VectorValue{3, et}] + reffe = BubbleRefFE(T, p) + test_reference_fe(reffe) + + N = num_components(T) + @test num_dofs(reffe) == N + @test Conformity(reffe) == L2Conformity() + @test get_polytope(reffe) == p + @test get_order(reffe) == max_order + + face_dofs = fill(Int[], num_faces(p)) + face_dofs[end] = 1:N + @test get_face_dofs(reffe) == face_dofs + + prebasis = get_prebasis(reffe) + @test length(prebasis) == N + @test prebasis isa LinearCombinationFieldVector + + shapefuns = get_shapefuns(reffe) + @test length(shapefuns) == N + + dofs = get_dof_basis(reffe) + xs = get_face_coordinates(p) + bxs = map(mean, xs) + bx0 = bxs[end] + # dof is at the barycenter of the polytope + for dof in dofs + @test bx0 == dof.point + end + # equal to 1 at the barycenter + val = evaluate(dofs, shapefuns) + @test val == one(val) + + # equal to 0 at the barycenters of each d < D faces + vals = evaluate(shapefuns, bxs[1:(end-1)]) + @test all(vals .== zero(T)) + end end # specific tests for TRI @@ -68,4 +71,36 @@ foo((x, y)) = 16*x*(1-x)*y*(1-y) @test all(map(_is_approx, evaluate(shapefuns, xs), foo.(xs))) +# Test API boundaries + +# MINI bubble + +order = 1 +@test ReferenceFE(SEGMENT, bubble, order) isa GenericRefFE{Bubble} +@test ReferenceFE(SEGMENT, bubble, Float64, order) isa GenericRefFE{Bubble} +@test_throws ErrorException ReferenceFE(SEGMENT, bubble, 2) + +# Custom terms + +coeffs = [ 1.;; ] +terms = [ CartesianIndex(6,2) ] +poly_order = 5 # maximum(6-1, 2-1) +reffe = BubbleRefFE(Float64, p; terms, coeffs) +@test get_order(reffe) == poly_order + +# wrong order with respect to terms +@test_throws AssertionError ReferenceFE(SEGMENT, bubble, 3; coeffs, terms) + +# dimension >0 +@test_throws ErrorException BubbleRefFE(Float64, VERTEX) + +# both terms and coeffs needed +@test_throws ErrorException BubbleRefFE(Float64, p; terms) +@test_throws ErrorException BubbleRefFE(Float64, p; coeffs) + +# wrong number of shapefuns +coeffs = [ 1. 1.; ] +terms = [ CartesianIndex(6,2) ] +@test_throws ErrorException BubbleRefFE(Float64, p; terms, coeffs) + end diff --git a/test/ReferenceFEsTests/CDLagrangianRefFEsTests.jl b/test/ReferenceFEsTests/CDLagrangianRefFEsTests.jl index 54bbc8bb2..e479c72ff 100644 --- a/test/ReferenceFEsTests/CDLagrangianRefFEsTests.jl +++ b/test/ReferenceFEsTests/CDLagrangianRefFEsTests.jl @@ -3,6 +3,7 @@ module CDLagrangianRefFEsTests using Gridap using Gridap.TensorValues using Gridap.ReferenceFEs +using Gridap.Polynomials using Test using Gridap.ReferenceFEs: _CDLagrangianRefFE diff --git a/test/ReferenceFEsTests/CLagrangianRefFEsTests.jl b/test/ReferenceFEsTests/CLagrangianRefFEsTests.jl index f2c6204f0..4748ff617 100644 --- a/test/ReferenceFEsTests/CLagrangianRefFEsTests.jl +++ b/test/ReferenceFEsTests/CLagrangianRefFEsTests.jl @@ -8,23 +8,25 @@ using Gridap.Polynomials using Gridap.ReferenceFEs using JSON +using Gridap.ReferenceFEs: monomial_basis + orders = (2,3) -b = MonomialBasis(Float64,QUAD,orders) +b = monomial_basis(Float64,QUAD,orders) r = [(0,0), (1,0), (2,0), (0,1), (1,1), (2,1), (0,2), (1,2), (2,2), (0,3), (1,3), (2,3)] @test get_exponents(b) == r orders = (1,1,2) -b = MonomialBasis(Float64,WEDGE,orders) +b = monomial_basis(Float64,WEDGE,orders) r = [(0,0,0), (1,0,0), (0,1,0), (0,0,1), (1,0,1), (0,1,1), (0,0,2), (1,0,2), (0,1,2)] @test get_exponents(b) == r orders = (1,1,1) -b = MonomialBasis(Float64,PYRAMID,orders) +b = monomial_basis(Float64,PYRAMID,orders) r = [(0,0,0), (1,0,0), (0,1,0), (1,1, 0), (0,0,1)] @test get_exponents(b) == r orders = (1,1,1) -b = MonomialBasis(Float64,TET,orders) +b = monomial_basis(Float64,TET,orders) r = [(0,0,0), (1,0,0), (0,1,0), (0,0,1)] @test get_exponents(b) == r @@ -71,7 +73,7 @@ dofs = LagrangianDofBasis(SymTensorValue{2,Int},VERTEX,()) dofs = LagrangianDofBasis(SymTracelessTensorValue{2,Int},VERTEX,()) @test dofs.node_and_comp_to_dof == SymTracelessTensorValue{2,Int}[(1,2)] -b = MonomialBasis(VectorValue{2,Int},VERTEX,()) +b = monomial_basis(VectorValue{2,Int},VERTEX,()) @test length(b) == 2 @test evaluate(b,Point{0,Int}[(),()]) == VectorValue{2,Int}[(1, 0) (0, 1); (1, 0) (0, 1)] @@ -127,6 +129,18 @@ d = 1 @test get_face_own_dofs(reffe,d) == [[5, 14], [6, 15], [7, 16], [8, 17]] @test get_face_own_nodes(reffe,d) == [[5], [6], [7], [8]] +# Same own dofs/nodes as with VectorValue, but testing othe polynomial +V = SymTracelessTensorValue{2,Float64} +reffe = LagrangianRefFE(V,QUAD,2; poly_type=Legendre) +test_reference_fe(reffe) +@test get_face_own_dofs(reffe,d) == [[5, 14], [6, 15], [7, 16], [8, 17]] +@test get_face_own_nodes(reffe,d) == [[5], [6], [7], [8]] + +V = SymTracelessTensorValue{2,Float64} +reffe = LagrangianRefFE(V,TRI,3; poly_type=Bernstein) +test_reference_fe(reffe) +@test get_face_own_dofs(reffe) == [[1, 11], [2, 12], [3, 13], [4, 5, 14, 15], [6, 7, 16, 17], [8, 9, 18, 19], [10, 20]] + # 0-order degenerated case orders = (0,0) @@ -220,4 +234,36 @@ f = joinpath(d,"reffe.jld2") to_jld2_file(reffe,f) @test reffe == from_jld2_file(typeof(reffe),f) +# Factory function +nodal = true +D = 2 +# o = 0 +reffe = ReferenceFE(TRI,lagrangian,0) +@test reffe == ReferenceFE(TRI,:P⁻, 1,D; nodal) # r=o-1, k=1 +reffe = ReferenceFE(QUAD,lagrangian,0) +@test reffe == ReferenceFE(QUAD,:Q⁻,1,D; nodal) # r=o+1, k=1 + +reffe = ReferenceFE(TRI,lagrangian,0; poly_type=Bernstein) +@test reffe == ReferenceFE(TRI,:P⁻, 1,D; nodal, poly_type=Bernstein) # r=o-1, k=1 +reffe = ReferenceFE(QUAD,lagrangian,0; poly_type=Legendre) +@test reffe == ReferenceFE(QUAD,:Q⁻,1,D; nodal, poly_type=Legendre) # r=o+1, k=1 + +V = SymTensorValue{2,Float64} +reffe = ReferenceFE(TRI, lagrangian,V,0; poly_type=Bernstein) +@test reffe == ReferenceFE(TRI,:P⁻, 1,D,V; nodal, poly_type=Bernstein) # r=o-1, k=1 +reffe = ReferenceFE(QUAD,lagrangian,V,0; poly_type=Legendre) +@test reffe == ReferenceFE(QUAD,:Q⁻,1,D,V; nodal, poly_type=Legendre) # r=o+1, k=1 + +@test_throws "Monomial" ReferenceFE(WEDGE,lagrangian,0; poly_type=Legendre) + +# o = 1 +reffe = ReferenceFE(TRI,lagrangian,1) +@test reffe == ReferenceFE(TRI,:P⁻,1,0; nodal) # r=o, k=0 +@test reffe == ReferenceFE(TRI,:P⁻,2,D; nodal) # r=o+1, k=D +@test reffe == ReferenceFE(TRI,:P, 1,0; nodal) # r=o, k=0 +@test reffe == ReferenceFE(TRI,:P, 1,D; nodal) # r=o, k=D +reffe = ReferenceFE(QUAD,lagrangian,1) +@test reffe == ReferenceFE(QUAD,:Q⁻, 1,0; nodal)# r=o, k=0 +@test reffe == ReferenceFE(QUAD,:Q⁻, 2,D; nodal)# r=o+1, k=D + end # module diff --git a/test/ReferenceFEsTests/CrouzeixRaviartFEsTests.jl b/test/ReferenceFEsTests/CrouzeixRaviartFEsTests.jl new file mode 100644 index 000000000..aa81d899a --- /dev/null +++ b/test/ReferenceFEsTests/CrouzeixRaviartFEsTests.jl @@ -0,0 +1,174 @@ +module CrouzeixRaviartTests + +using Test +using Gridap +using Gridap.ReferenceFEs, Gridap.Geometry, Gridap.FESpaces, Gridap.Arrays, Gridap.TensorValues +using Gridap.Helpers + +# Only defined for simplices and order 1 +@test_throws ErrorException CrouzeixRaviartRefFE(Float64,QUAD,1) +@test_throws ErrorException CrouzeixRaviartRefFE(Float64,TRI,2) + +reffe = ReferenceFE(TRI, crouzeix_raviart, 1) +reffec = CrouzeixRaviartRefFE(Float64, TRI, 1) +@test reffe == reffec +test_reference_fe(reffe) +@test Conformity(reffe, :L2) == L2Conformity() +@test_throws ErrorException Conformity(reffe, :H1) + +reffe = ReferenceFE(TET, crouzeix_raviart, 1) +reffec = CrouzeixRaviartRefFE(Float64, TET, 1) +@test reffe == reffec +test_reference_fe(reffe) +@test Conformity(reffe, :L2) == L2Conformity() +@test_throws ErrorException Conformity(reffe, :H1) + +function solve_crScalarPoisson(partition, cells, u_exact) + f(x) = - Δ(u_exact)(x) + + model = simplexify(CartesianDiscreteModel(partition, cells)) + + # reffe = CrouzeixRaviartRefFE(VectorValue{2,Float64},TRI,1) + reffe = CrouzeixRaviartRefFE(Float64,TRI,1) + V = FESpace(model,reffe,dirichlet_tags="boundary") + U = TrialFESpace(V,u_exact) + + Ω = Triangulation(model) + dΩ = Measure(Ω,3) + + a(u,v) = ∫( ∇(u)⋅∇(v) )*dΩ + l(v) = ∫( f*v )*dΩ + + op = AffineFEOperator(a,l,U,V) + uh = solve(op) + e = uh-u_exact + + return sqrt(sum( ∫( e⋅e )*dΩ )) +end + + +function solve_crVectorPoisson(partition, cells, u_exact) + f(x) = - Δ(u_exact)(x) + + model = simplexify(CartesianDiscreteModel(partition, cells)) + + reffe = CrouzeixRaviartRefFE(VectorValue{2,Float64},TRI,1) + V = FESpace(model,reffe,dirichlet_tags="boundary") + U = TrialFESpace(V, u_exact) + + Ω = Triangulation(model) + dΩ = Measure(Ω,3) + + a(u,v) = ∫( ∇(u) ⊙ ∇(v) )*dΩ + l(v) = ∫( f ⋅ v )*dΩ + + op = AffineFEOperator(a,l,U,V) + uh = solve(op) + e = uh-u_exact + + return sqrt(sum( ∫( e⋅e )*dΩ )) +end + +function solve_crStokes(partition, cells, u_exact, p_exact) + f(x) = - Δ(u_exact)(x) + ∇(p_exact)(x) + model = simplexify(CartesianDiscreteModel(partition, cells)) + + reffe_u = CrouzeixRaviartRefFE(VectorValue{2,Float64},TRI,1) + reffe_p = ReferenceFE(lagrangian, Float64, 0; space=:P) + V = FESpace(model,reffe_u,dirichlet_tags="boundary") + Q = FESpace(model,reffe_p,conformity=:L2,constraint=:zeromean) + Y = MultiFieldFESpace([V,Q]) + + U = TrialFESpace(V, u_exact) + P = TrialFESpace(Q) + X = MultiFieldFESpace([U,P]) + + Ω = Triangulation(model) + dΩ = Measure(Ω,3) + + a((u,p),(v,q)) = ∫( ∇(v)⊙∇(u) - (∇⋅v)*p + q*(∇⋅u) )dΩ + l((v,q)) = ∫( v⋅f )dΩ + + op = AffineFEOperator(a,l,X,Y) + uh, ph = solve(op) + eu = uh-u_exact + ep = ph-p_exact + + return sqrt(sum( ∫( eu⋅eu )*dΩ )), sqrt(sum( ∫( ep⋅ep )*dΩ )) +end + +function conv_test_Stokes(partition,ns,u,p) + el2u = Float64[] + el2p = Float64[] + hs = Float64[] + for n in ns + l2u, l2p = solve_crStokes(partition,(n,n),u,p) + h = 1.0/n + push!(el2u,l2u) + push!(el2p,l2p) + push!(hs,h) + end + #println(el2u) + #println(el2p) + el2u, el2p, hs +end + +function conv_test_Poisson(partition,ns,u) + el2 = Float64[] + hs = Float64[] + for n in ns + l2 = solve_crScalarPoisson(partition,(n,n),u) + #println(l2) + h = 1.0/n + push!(el2,l2) + push!(hs,h) + end + #println(el2) + el2, hs +end + +function slope(hs,errors) + x = log10.(hs) + y = log10.(errors) + linreg = hcat(fill!(similar(x), 1), x) \ y + linreg[2] +end + + +partition = (0,1,0,1) + +# Stokes +u_exact(x) = VectorValue( [sin(pi*x[1])^2*sin(pi*x[2])*cos(pi*x[2]), -sin(pi*x[2])^2*sin(pi*x[1])*cos(pi*x[1])] ) +p_exact(x) = sin(2*pi*x[1])*sin(2*pi*x[2]) + +# Scalar Poisson +u_exact_p(x) = sin(2*π*x[1])*sin(2*π*x[2]) + +# Vector Poisson +# u_exact(x) = VectorValue( [sin(2*π*x[1])*sin(2*π*x[2]), x[1]*(x[1]-1)*x[2]*(x[2]-1)] ) + +ns = [4,8,16,32,64] + +el, hs = conv_test_Poisson(partition,ns,u_exact_p) +# println("Slope L2-norm u_Poisson: $(slope(hs,el))") + +elu, elp, hs = conv_test_Stokes(partition,ns,u_exact,p_exact) +@test slope(hs,elu) >= 1.9 +@test slope(hs,elp) >= 0.9 +@test slope(hs,el) >= 1.9 + +#println("Slope L2-norm u_Stokes: $(slope(hs,elu))") +#println("Slope L2-norm p_Stokes: $(slope(hs,elp))") +#println("Slope L2-norm u_Poisson: $(slope(hs,el))") + + +# for fun to test _get_dfaces_measure in 4D +SIMPL4 = ExtrusionPolytope(tfill(TET_AXIS,Val(4))) +reffe = ReferenceFE(SIMPL4, crouzeix_raviart, 1) +reffec = CrouzeixRaviartRefFE(Float64, SIMPL4, 1) +@test reffe == reffec +@test Conformity(reffe, :L2) == L2Conformity() +@test_throws ErrorException Conformity(reffe, :H1) + +end # module + diff --git a/test/ReferenceFEsTests/DofsTests.jl b/test/ReferenceFEsTests/DofsTests.jl index 5490f0956..91c084df1 100644 --- a/test/ReferenceFEsTests/DofsTests.jl +++ b/test/ReferenceFEsTests/DofsTests.jl @@ -1,3 +1,41 @@ module DofsTests +using Test +using Gridap +using Gridap.ReferenceFEs +using Gridap.Fields +using FillArrays + +T = Float64 + +reffe0 = ReferenceFE(SEGMENT, Lagrangian(), Float64, 2) +reffe1 = ReferenceFE(SEGMENT, Lagrangian(), Float64, 3) + +dofs = get_dof_basis(reffe0) # length 3 +@test dofs isa AbstractVector{<:Dof} + +dofs_lc = linear_combination(Eye(3,6), dofs) # length 6 +@test dofs_lc isa AbstractVector{<:Dof} +@test length(dofs_lc) == 6 + +basis = get_prebasis(reffe1) # length 4 +@test basis isa AbstractVector{<:Field} +basis_lc = linear_combination(Eye(4,5), basis) # length 5 + +@test basis_lc isa AbstractVector{<:Field} +@test length(basis_lc) == 5 + +M34 = evaluate(dofs, basis ) +M35 = evaluate(dofs, basis_lc) +M64 = evaluate(dofs_lc, basis ) +M65 = evaluate(dofs_lc, basis_lc) + +@test size(M34) == (3, 4) +@test size(M35) == (3, 5) +@test size(M64) == (6, 4) +@test size(M65) == (6, 5) +@test M35[1:3,1:4] == M34 +@test M64[1:3,1:4] == M34 +@test M65[1:3,1:4] == M34 + end # module diff --git a/test/ReferenceFEsTests/ExtrusionPolytopesTests.jl b/test/ReferenceFEsTests/ExtrusionPolytopesTests.jl index 053694483..99d304bb9 100644 --- a/test/ReferenceFEsTests/ExtrusionPolytopesTests.jl +++ b/test/ReferenceFEsTests/ExtrusionPolytopesTests.jl @@ -52,9 +52,9 @@ x = Point{3,Float64}[(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)] p = SEGMENT test_polytope(p,optional=true) -@test get_vertex_coordinates(p) == VectorValue{1,Float64}[(0),(1)] -@test get_edge_tangent(p) == VectorValue{1,Float64}[(1)] -@test get_facet_normal(p) == VectorValue{1,Float64}[(-1),(1)] +@test get_vertex_coordinates(p) == VectorValue{1,Float64}[(0,),(1,)] +@test get_edge_tangent(p) == VectorValue{1,Float64}[(1,)] +@test get_facet_normal(p) == VectorValue{1,Float64}[(-1,),(1,)] perm = get_vertex_permutations(p) @test perm == [[1, 2], [2, 1]] diff --git a/test/ReferenceFEsTests/GeometricDecompositionTests.jl b/test/ReferenceFEsTests/GeometricDecompositionTests.jl new file mode 100644 index 000000000..46c606cca --- /dev/null +++ b/test/ReferenceFEsTests/GeometricDecompositionTests.jl @@ -0,0 +1,462 @@ + +module GeometricDecompositionTest + +using Test +using Gridap.Helpers +using Gridap.Polynomials +using Gridap.ReferenceFEs +using Gridap.Fields +using Gridap.TensorValues +using Gridap.Fields: MockField +using Gridap.ReferenceFEs +using Gridap.Geometry +using Gridap.CellData +using Gridap.Arrays +using Gridap.Visualization + +using StaticArrays +using LinearAlgebra + +using Gridap +using Gridap.FESpaces + +using Gridap.ReferenceFEs: FaceMeasure, set_face! + +import Gridap.Arrays: return_cache +import Gridap.Arrays: evaluate! + +########################## +# FaceIntegralFormVector # +########################## + +# The MomentBasedDofBasis cannot implement the L2 trace norms on +# Polytope faces, because the norm is nonlinear. An alternative implementation is +# introduced here to test the trace norms of the geometrically decomposed bases. + +const _μ = MonomialBasis(Val(0),Float64,0) # a length-1 dummy basis that won't be evaluated + +""" + abstract type Form <: Map + +Abstract type for a form over a functional space (typically a polynomial space). +The domain is a [`Field`](@ref) set and the range the scalar set. + +If put in src, this should be a supertype of `Dof`, the latter being a *linear* `Form`. +""" +abstract type Form <: Map end + +""" + FaceIntegralFormVector( + p::Polytope{D}, + order::Int, + faces_and_forms_integrands::Vector{Tuple{Vector{Int},Function}} + ) + +Constructs a vector of (nonlinear) forms that are integrals over faces of `p`. + +`faces_and_forms_integrands` is a collection of form definitions, each one is +given by a couple (faces_ids, σ) where + - faces_ids is vector of ids of faces Fₖ of `p` + - σ is the form integrand φ,_,ds -> σ(φ,_,ds) that returns a scalar-valued Field-like object to be integrated over each Fₖ + +The forms are thus defined by φ -> ∫_Fₖ σ(φ,_,ds). + +In the final basis, the forms are ordered by moment descriptor, then by face. + +We are assuming that all the faces in a moment are of the same type. +""" +struct FaceIntegralFormVector{T,FI,DS} <: AbstractVector{Form} + form_faces::Vector{Vector{NTuple{2,Int}}} # f -> faces over which f is integrated computed + form_integrands::FI # i -> σᵢ + form_measures::DS # tuple of `FaceMeasure`s + face_own_forms::Vector{Vector{Int}} # "inverse" of form_faces + + function FaceIntegralFormVector(faces,integrands,measures,f_own_forms) + T = Float64 # TODO + FI = typeof(integrands) + DS = typeof(measures) + new{T,FI,DS}(faces,integrands,measures,f_own_forms) + end + + function FaceIntegralFormVector(p::Polytope,order::Int,forms) + n_faces = num_faces(p) + n_form = length(forms) + face_dims = get_facedims(p) + face_offsets = get_offsets(p) + reffaces, face_types = ReferenceFEs._compute_reffaces_and_face_types(p) + + # Create facemeasures and integrand for each integral form + # Count number of forms per face + # compute local to global form index + integrands = () + measures = () + face_n_forms = zeros(Int,n_faces) + face_form_ids = Vector{Vector{NTuple{2,Int}}}(undef,n_form) + form_I = 0 + for (k,(faces,σ)) in enumerate(forms) + face_ids = Vector{NTuple{2,Int}}(undef, length(faces)) + for (i,face) in enumerate(faces) + d = face_dims[face] + lface = face - face_offsets[d+1] + face_ids[i] = (form_I + i, lface) + end + face_form_ids[k] = face_ids + form_I += length(faces) + + face_n_forms[faces] .+= 1 + integrands = (integrands..., σ) + + ftype = face_types[first(faces)] + @check all(isequal(ftype), face_types[faces]) + fp = reffaces[ftype] + measures = (measures..., FaceMeasure(p,fp,order) ) + end + + # Compute face forms and nodes indices + n_forms = 0 + face_own_forms = Vector{Vector{Int}}(undef,n_faces) + for face in 1:n_faces + n_forms_i = face_n_forms[face] + face_own_forms[face] = collect((n_forms+1):(n_forms+n_forms_i)) + n_forms += n_forms_i + end + + FaceIntegralFormVector(face_form_ids,integrands,measures,face_own_forms) + end +end + +Base.size(a::FaceIntegralFormVector) = (num_forms(a),) +Base.axes(a::FaceIntegralFormVector) = (Base.OneTo(num_forms(a)),) +Base.getindex(::FaceIntegralFormVector,::Integer) = Form() +Base.IndexStyle(::FaceIntegralFormVector) = IndexLinear() +num_forms(b::FaceIntegralFormVector) = mapreduce(length, +, b.face_own_forms) + +function return_cache(b::FaceIntegralFormVector{T}, f) where {T} + cms = () + for (_,σ,ds) in zip(b.form_faces, b.form_integrands, b.form_measures) + cms = ( cms..., return_cache(σ,f,_μ,ds)) + end + + r = Array{T}(undef, (num_forms(b), size(f)...)) + c = CachedArray(r) + return cms, c +end + +function evaluate!(cache, b::FaceIntegralFormVector, f::AbstractVector{<:Field}) + cms, c = cache + nf = length(f) + setsize!(c, (length(b), nf)) + + forms_vals = c.array + for (cds,faces,σ,ds) in zip(cms, b.form_faces, b.form_integrands, b.form_measures) + for (form_id, lface) in faces + set_face!(ds,lface) + # vals: v_{i,1,j} = σ(f_j(n_i), _μ₁, ds), size (nN, 1, nf) + vals, _ = evaluate!(cds,σ,f,_μ,ds) + forms_vals[form_id,:] = reshape(sum(vals,dims=1), (nf,)) + end + end + forms_vals +end + + +########################################################################################### +# Traces L2-norm descriptors and geometric decompositions trace-forms for each conformity # +########################################################################################### + +function zero_moment(φ,_,ds) # zero_f(φ) = ∫ 0 df + C0 = ConstantField(0) + φ0 = Broadcasting(Operation(*))(C0,φ) + Broadcasting(Operation(norm))(φ0) +end + +function HGrad_face_tr_norm(φ,_,ds) # tr_f(φ) = ∫ ‖φ‖ df + Broadcasting(Operation(norm))(φ) +end + +function HCurl_edge_tr_norm(φ,_,ds) # tr_E(φ) = ∫ ‖φ·t‖ dE + t = get_edge_tangent(ds) + φt = Broadcasting(Operation(⋅))(t,φ) + Broadcasting(Operation(norm))(φt) +end + +function HCurl_facet_tr_norm(φ,_,ds) # tr_F(φ) = ∫ ‖φ×n‖ dF + n = get_facet_normal(ds) + φt = Broadcasting(Operation(×))(φ,n) + Broadcasting(Operation(norm))(φt) +end + +function HDiv_facet_tr_norm(φ,_,ds) # tr_F(φ) = ∫ ‖φ⋅n‖ dF + n = get_facet_normal(ds) + φn = Broadcasting(Operation(⋅))(φ,n) + Broadcasting(Operation(norm))(φn) +end + +function HDiv_facet_flux(φ,_,ds) # ∫ φ⋅n dF + n = get_facet_normal(ds) + Broadcasting(Operation(⋅))(φ,n) +end + + +# zero form on each face +function get_trace_forms(p::Polytope{D}, field, ::L2Conformity) where D + face_ranges = get_dimranges(p) + trace_forms = Tuple[ (f_range, zero_moment) for f_range in face_ranges ] + qorder = 2*get_order(field)+1 + FaceIntegralFormVector(p,qorder,trace_forms) +end + +# norm form on each boundary face, zero inside +function get_trace_forms(p::Polytope{D}, field, ::GradConformity) where D + face_ranges = get_dimranges(p) + trace_forms = Tuple[ (face_ranges[d], HGrad_face_tr_norm) for d in 1:D ] + push!(trace_forms, (last(face_ranges), zero_moment)) + qorder = 2*get_order(field)+1 + FaceIntegralFormVector(p,qorder,trace_forms) +end + +# zero form on vertices and inside, norm of tangential trace on edges (φ⋅t) and facets (φ×n) +function get_trace_forms(p::Polytope{D}, field, ::CurlConformity) where D + D>3 && @notimplemented "tangential trace to 2D faces in D>3 notimplemented" + face_ranges = get_dimranges(p) + trace_forms = Tuple[ (first(face_ranges), zero_moment) ] + D≥2 && push!(trace_forms, (face_ranges[2], HCurl_edge_tr_norm)) + D≥3 && push!(trace_forms, (face_ranges[D], HCurl_facet_tr_norm)) + push!( trace_forms, (last( face_ranges), zero_moment)) + qorder = 2*get_order(field)+1 + FaceIntegralFormVector(p,qorder,trace_forms) +end + +# zero form on vertices, edges and inside, norm of normal trace to facets (φ⋅n) +function get_trace_forms(p::Polytope{D}, field, ::DivConformity) where D + face_ranges = get_dimranges(p) + trace_forms = Tuple[ (face_ranges[d], zero_moment) for d in 1:D-1 ] + push!(trace_forms, (face_ranges[D], HDiv_facet_tr_norm)) + push!(trace_forms, (last( face_ranges), zero_moment)) + qorder = 2*get_order(field)+1 + FaceIntegralFormVector(p,qorder,trace_forms) +end + +function get_normal_flux_forms(p::Polytope{D}, field) where D + facet_range = get_dimrange(p,D-1) + fun_flux_form = Tuple[ (facet_range, HDiv_facet_flux) ] + qorder = get_order(field)+2 + FaceIntegralFormVector(p,qorder,fun_flux_form) +end + + +# Traces Test function +function _test_geometric_decomposition(b,p,conf, + face_own_funs=get_face_own_funs(b,p,conf), skip_check=false) + + if !skip_check + @test has_geometric_decomposition(b,p,conf) + end + + faces = get_faces(p) + tr_forms = get_trace_forms(p,b,conf) # one form for each face in faces + b_face_traces = evaluate(tr_forms, b) + tr_iszero = map(b_tr -> abs(b_tr) airy(f,x) + +ϕ(x) = x[1]^3 - 3.0*x[2]^2 + 2.0*x[1]*x[2] +u(x) = airy(ϕ)(x) + +p = TRI +D = num_dims(p) +order = 0 + +reffe = ReferenceFEs.HellanHerrmannJhonsonRefFE(Float64,p,order) +dofs_ref = get_dof_basis(reffe) +basis_ref = get_shapefuns(reffe) +prebasis = get_prebasis(reffe) +get_face_own_dofs(reffe) +get_face_own_dofs_permutations(reffe) + +M = evaluate(dofs_ref, basis_ref) +@test M ≈ I(num_dofs(reffe)) + +model = simplexify(CartesianDiscreteModel((0,1,0,1), (1,1))) +#model = nonpermutedmodel() +topo = get_grid_topology(model) +cell_to_nodes = Geometry.get_faces(topo,2,0) +cell_to_faces = Geometry.get_faces(topo,2,1) +face_to_nodes = Geometry.get_faces(topo,1,0) +face_to_cells = Geometry.get_faces(topo,1,2) +perms = get_cell_permutations(topo) +cmaps = get_cell_map(get_grid(model)) +Jt = lazy_map(Broadcasting(∇),cmaps) + +pts = [VectorValue(0.,0.),VectorValue(1.0,0.),VectorValue(0.,1.)] + +Ω = Triangulation(model) +Γ = Boundary(model) +Γ.glue.face_to_bgface +Λ = Skeleton(model) +Λ.plus.glue.face_to_lcell +Λ.minus.glue.face_to_lcell + +dΩ = Measure(Ω,4*(order+1)) +dΓ = Measure(Γ,4*(order+1)) +dΛ = Measure(Λ,4*(order+1)) + +V = FESpace(model,reffe) +get_cell_dof_ids(V) + +uh = interpolate(u,V) + +x = zeros(num_free_dofs(V)) +#x[[1,2,4,5]] .= 1.0 +x[3] = 1.0 +uh = FEFunction(V,x) +get_cell_dof_values(uh) + +uh_bis = interpolate(uh,V) +get_cell_dof_values(uh_bis) + +cf = uh +get_free_dof_values(cf) + +#print_op_tree(cf.cell_field.cell_field) + +#μ_c = MonomialBasis(Val(2),SymTensorValue{2,Float64},order-1,Polynomials._p_filter) +#μ_c = get_shapefuns(LagrangianRefFE(SymTensorValue{2,Float64}, TRI, order-1)) +μ_c = map(constant_field,[SymTensorValue(0.,1.,0.),SymTensorValue(-2.,1.,0.),SymTensorValue(0.,-1.,2.)]) +μ_Ω = GenericCellField([μ_c,μ_c],Ω,ReferenceDomain()) +collect(get_array(∫((cf⊙transpose(μ_Ω)))dΩ)) + +# μ_f = MonomialBasis(Val(1),Float64,order,Polynomials._p_filter) +μ_f = get_shapefuns(LagrangianRefFE(Float64, SEGMENT, order)) +μ_Γ = GenericCellField([μ_f,μ_f,μ_f,μ_f],Γ,ReferenceDomain()) +μ_Λ = GenericCellField([μ_f],Λ,ReferenceDomain()) +nΓ = get_normal_vector(Γ) +nΛ = get_normal_vector(Λ) + +collect(get_array(∫(nΓ⋅(cf⋅nΓ)*μ_Γ)dΓ)) +collect(get_array(∫((nΛ⋅(cf⋅nΛ)).plus - (nΛ⋅(cf⋅nΛ)).minus)dΛ)) +collect(get_array(∫((nΛ⋅(cf⋅nΛ)).plus)dΛ)) +collect(get_array(∫((nΛ⋅(cf⋅nΛ)).minus)dΛ)) + +pts_Ω = get_cell_points(dΩ.quad) +pts_Γ = get_cell_points(dΓ.quad) +pts_Λ = get_cell_points(dΛ.quad) + +cf_Λ = cf#(cf⋅nΛ) +cf_Λ_plus = nΛ.plus⋅(cf.plus⋅nΛ.plus) +cf_Λ_minus = nΛ.minus⋅(cf.minus⋅nΛ.minus) + +cf_plus = cf.plus +cf_minus = cf.minus + +cf_Λ.plus(pts_Λ) +cf_Λ.minus(pts_Λ) + +cf_Λ_plus(pts_Λ) +cf_Λ_minus(pts_Λ) + +cf_plus(pts_Λ)[1] +cf_minus(pts_Λ)[1] + +cf_plus_Λ = change_domain(cf_plus,Λ,ReferenceDomain()) +print_op_tree(get_data(cf_plus_Λ)) +cf_plus_Λ(pts_Λ) + + +pts = get_cell_points(dΩ.quad) +dofs_phys = get_fe_dof_basis(V) +basis_phys = get_fe_basis(V) +map(a -> round.(a),collect(dofs_phys(basis_phys)))[2] +y = collect(basis_phys(pts)) + +reffe_lag = LagrangianRefFE(SymTensorValue{2,Float64},TRI,1) diff --git a/test/ReferenceFEsTests/LagrangianRefFEsTests.jl b/test/ReferenceFEsTests/LagrangianRefFEsTests.jl index 98b4d42f3..75ba8a86a 100644 --- a/test/ReferenceFEsTests/LagrangianRefFEsTests.jl +++ b/test/ReferenceFEsTests/LagrangianRefFEsTests.jl @@ -8,7 +8,7 @@ using Gridap.Fields D = 2 T = Float64 order = 1 -prebasis = MonomialBasis{D}(T,order) +prebasis = MonomialBasis(Val(D),T,order) polytope = QUAD x = get_vertex_coordinates(polytope) diff --git a/test/ReferenceFEsTests/ModalScalarRefFEsTests.jl b/test/ReferenceFEsTests/ModalScalarRefFEsTests.jl new file mode 100644 index 000000000..fe81c9cbb --- /dev/null +++ b/test/ReferenceFEsTests/ModalScalarRefFEsTests.jl @@ -0,0 +1,59 @@ +module ModalScalarRefFEsTests + +using Test +using Gridap.Fields +using Gridap.ReferenceFEs +using Gridap.Polynomials +using Gridap.TensorValues: SymTensorValue +using Gridap.Io + +nodal = false + +for (p,F) in [ + (TRI, :P⁻), (TRI, :P), (QUAD,:Q⁻), (QUAD,:S), + (TET, :P⁻), (TET, :P), (HEX, :Q⁻), (HEX, :S), + ] + + reffe = ModalScalarRefFE(Float64,p,1; F) + @test reffe == ReferenceFE(p,F,1,0) + @test reffe == ReferenceFE(p,F,1,0; nodal) + test_reference_fe(reffe) + + reffe = ModalScalarRefFE(Float64,p,4; F) + @test reffe == ReferenceFE(p,F,4,0) + @test reffe == ReferenceFE(p,F,4,0; nodal) + test_reference_fe(reffe) + + V = SymTensorValue{2,Float64} + reffe = ModalScalarRefFE(V,p,2; F) + @test reffe == ReferenceFE(p,F,2,0,V) + @test reffe == ReferenceFE(p,F,2,0,V; nodal) + test_reference_fe(reffe) +end + +poly_type=Polynomials.ModalC0 +change_dof=true + +order = 4 +reffe = ModalScalarRefFE(Float64,HEX,order; F=:S) +test_reference_fe(reffe) +@test reffe == ReferenceFE(HEX,:S,4,0; nodal) +@test get_order(reffe) == order +@test Conformity(reffe,:H1) == GradConformity() +@test Conformity(reffe,:L2) == L2Conformity() + +@test_throws "hierarchical" reffe = ModalScalarRefFE(Float64,HEX,order; F=:S, poly_type=Bernstein) + +reffe = ModalScalarRefFE(Float64,HEX,order; F=:S, poly_type) +test_reference_fe(reffe) +@test reffe == ReferenceFE(HEX,:S,4,0; poly_type) +@test reffe == ReferenceFE(HEX,:S,4,0; poly_type, nodal) + +reffe = ModalScalarRefFE(Float64,HEX,order; F=:S, poly_type, change_dof) +test_reference_fe(reffe) +@test reffe == ReferenceFE(HEX,:S,4,0; poly_type, change_dof) +@test reffe == ReferenceFE(HEX,:S,4,0; poly_type, change_dof, nodal) + +@test_warn "falling back to `change_dof=false`" ModalScalarRefFE(Float64,HEX,order; F=:S, poly_type=Monomial, change_dof) + +end # module diff --git a/test/ReferenceFEsTests/MomentBasedReferenceFEs.jl b/test/ReferenceFEsTests/MomentBasedReferenceFEs.jl new file mode 100644 index 000000000..1ca1bffcc --- /dev/null +++ b/test/ReferenceFEsTests/MomentBasedReferenceFEs.jl @@ -0,0 +1,5 @@ + +using Gridap + +using Gridap.ReferenceFEs + diff --git a/test/ReferenceFEsTests/NedelecRefFEsTests.jl b/test/ReferenceFEsTests/NedelecRefFEsTests.jl index 1e0db175c..2319559ca 100644 --- a/test/ReferenceFEsTests/NedelecRefFEsTests.jl +++ b/test/ReferenceFEsTests/NedelecRefFEsTests.jl @@ -8,6 +8,11 @@ using Gridap.TensorValues using Gridap.Fields: MockField using Gridap.ReferenceFEs + +@test Nedelec{1}() == nedelec +@test Nedelec{1}() == nedelec1 +@test Nedelec{2}() == nedelec2 + p = QUAD D = num_dims(QUAD) et = Float64 @@ -15,12 +20,14 @@ order = 0 reffe = NedelecRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 4 -@test get_order(get_prebasis(reffe)) == 0 +@test length(get_prebasis(reffe)) == 4 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 4 @test Conformity(reffe) == CurlConformity() +@test_warn "falling back to `change_dof=false`" NedelecRefFE(et,p,order; change_dof=true, poly_type=Monomial) + p = QUAD D = num_dims(QUAD) et = Float64 @@ -28,9 +35,9 @@ order = 1 reffe = NedelecRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 12 +@test length(get_prebasis(reffe)) == 12 @test num_dofs(reffe) == 12 -@test get_order(get_prebasis(reffe)) == 1 +@test get_order(get_prebasis(reffe)) == 2 prebasis = get_prebasis(reffe) dof_basis = get_dof_basis(reffe) @@ -46,6 +53,36 @@ cache = return_cache(dof_basis,prebasis) r = evaluate!(cache, dof_basis, prebasis) test_dof_array(dof_basis,prebasis,r) +order = 3 +reffe = NedelecRefFE(et,p,order) +test_reference_fe(reffe) + +p = HEX +D = num_dims(HEX) +et = Float64 +order = 2 + +reffe = NedelecRefFE(et,p,order) +test_reference_fe(reffe) +@test length(get_prebasis(reffe)) == 144 +@test num_dofs(reffe) == 144 +@test get_order(get_prebasis(reffe)) == 3 + +prebasis = get_prebasis(reffe) +dof_basis = get_dof_basis(reffe) + +v = VectorValue(3.0,1.0,-2.) +field = GenericField(x->v*x[1]*x[2]*x[3]) + +cache = return_cache(dof_basis,field) +r = evaluate!(cache, dof_basis, field) +test_dof_array(dof_basis,field,r) + +cache = return_cache(dof_basis,prebasis) +r = evaluate!(cache, dof_basis, prebasis) +test_dof_array(dof_basis,prebasis,r) + + p = TET D = num_dims(p) et = Float64 @@ -53,8 +90,8 @@ order = 0 reffe = NedelecRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 6 -@test get_order(get_prebasis(reffe)) == 0 +@test length(get_prebasis(reffe)) == 6 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 6 @test Conformity(reffe) == CurlConformity() @@ -94,12 +131,65 @@ order = 0 reffe = NedelecRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 3 -@test get_order(get_prebasis(reffe)) == 0 +@test length(get_prebasis(reffe)) == 3 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 3 @test Conformity(reffe) == CurlConformity() dof_basis = get_dof_basis(reffe) +order = 3 + +reffe = NedelecRefFE(et,p,order) +test_reference_fe(reffe) +@test length(get_prebasis(reffe)) == 24 +@test get_order(get_prebasis(reffe)) == 4 +@test num_dofs(reffe) == 24 +@test Conformity(reffe) == CurlConformity() +dof_basis = get_dof_basis(reffe) + +# tests for Nedelec elements of the second kind +@test_throws AssertionError NedelecRefFE(Float64, QUAD, 1; kind=2) +@test_throws AssertionError NedelecRefFE(Float64, HEX, 2; kind=2) +@test_throws AssertionError NedelecRefFE(Float64, TRI, 0; kind=2) + +p = TRI +et = Float64 +order = 1 + +reffe2 = NedelecRefFE(et,p,order; kind=2) +test_reference_fe(reffe2) +@test length(get_prebasis(reffe2)) == 6 +@test get_order(get_prebasis(reffe2)) == 1 +@test num_dofs(reffe2) == 6 +@test Conformity(reffe2) == CurlConformity() +dof_basis = get_dof_basis(reffe2) + +p = TRI +et = Float64 +order = 2 + +reffe2 = NedelecRefFE(et,p,order; kind=2) +test_reference_fe(reffe2) +@test length(get_prebasis(reffe2)) == 12 +@test get_order(get_prebasis(reffe2)) == 2 +@test num_dofs(reffe2) == 12 +@test Conformity(reffe2) == CurlConformity() +dof_basis = get_dof_basis(reffe2) + + +p = TET +et = Float64 +order = 3 + +reffe2 = NedelecRefFE(et,p,order; kind=2) +test_reference_fe(reffe2) +@test length(get_prebasis(reffe2)) == 60 +@test get_order(get_prebasis(reffe2)) == 3 +@test num_dofs(reffe2) == 60 +@test Conformity(reffe2) == CurlConformity() +dof_basis = get_dof_basis(reffe2) + + #using Gridap.Geometry #using Gridap.Visualization #grid = compute_reference_grid(p,10) @@ -114,14 +204,16 @@ dof_basis = get_dof_basis(reffe) # Factory function reffe = ReferenceFE(QUAD,nedelec,0) -@test num_terms(get_prebasis(reffe)) == 4 -@test get_order(get_prebasis(reffe)) == 0 +@test length(get_prebasis(reffe)) == 4 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 4 @test Conformity(reffe) == CurlConformity() +@test_warn "falling back to `change_dof=false`" ReferenceFE(QUAD,nedelec,0; poly_type=Monomial) + reffe = ReferenceFE(QUAD,nedelec,Float64,0) -@test num_terms(get_prebasis(reffe)) == 4 -@test get_order(get_prebasis(reffe)) == 0 +@test length(get_prebasis(reffe)) == 4 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 4 @test Conformity(reffe) == CurlConformity() @@ -129,7 +221,6 @@ reffe = ReferenceFE(QUAD,nedelec,Float64,0) @test Conformity(reffe,:Hcurl) == CurlConformity() @test Conformity(reffe,:HCurl) == CurlConformity() -@test Nedelec() == nedelec p = TET D = num_dims(p) @@ -137,31 +228,85 @@ et = Float64 order = 1 reffe = NedelecRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 20 -@test get_order(get_prebasis(reffe)) == 1 +@test length(get_prebasis(reffe)) == 20 +@test get_order(get_prebasis(reffe)) == 2 @test num_dofs(reffe) == 20 @test Conformity(reffe) == CurlConformity() dof_basis = get_dof_basis(reffe) face_odofs_L2 = get_face_own_dofs(reffe,L2Conformity()) -@test face_odofs_L2 == [Int64[], Int64[], Int64[], Int64[], - Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], +@test face_odofs_L2 == [Int64[], Int64[], Int64[], Int64[], + Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], collect(1:20)] face_odofs = get_face_own_dofs(reffe) face_cdofs = get_face_dofs(reffe) -@test face_odofs == [Int64[], Int64[], Int64[], Int64[], - [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], [15, 16], [17, 18], [19, 20], +@test face_odofs == [Int64[], Int64[], Int64[], Int64[], + [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], [15, 16], [17, 18], [19, 20], Int64[]] -@test face_cdofs == [Int64[], Int64[], Int64[], Int64[], - [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], - [1, 2, 3, 4, 5, 6, 13, 14], [1, 2, 7, 8, 9, 10, 15, 16], [3, 4, 7, 8, 11, 12, 17, 18], [5, 6, 9, 10, 11, 12, 19, 20], +@test face_cdofs == [Int64[], Int64[], Int64[], Int64[], + [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], + [1, 2, 3, 4, 5, 6, 13, 14], [1, 2, 7, 8, 9, 10, 15, 16], [3, 4, 7, 8, 11, 12, 17, 18], [5, 6, 9, 10, 11, 12, 19, 20], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]] - +p = TET +D = num_dims(p) +et = Float64 +order = 3 +reffe = NedelecRefFE(et,p,order) +test_reference_fe(reffe) +@test length(get_prebasis(reffe)) == 84 +@test get_order(get_prebasis(reffe)) == 4 +@test num_dofs(reffe) == 84 +@test Conformity(reffe) == CurlConformity() +dof_basis = get_dof_basis(reffe) + +face_odofs_L2 = get_face_own_dofs(reffe,L2Conformity()) + +@test face_odofs_L2 == [Int64[], Int64[], Int64[], Int64[], + Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], Int64[], + collect(1:84)] + +face_odofs = get_face_own_dofs(reffe) +face_cdofs = get_face_dofs(reffe) + +@test face_odofs == [Int64[], Int64[], Int64[], Int64[], + [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16], [17, 18, 19, 20], [21, 22, 23, 24], + [25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36], # facet 123 + [37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48], # facet 124 + [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60], # facet 134 + [61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72], # facet 234 + [73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84]] # cell + +# Factory function: r=1, k=1 +reffe = ReferenceFE(QUAD,nedelec,0) +@test reffe == ReferenceFE(QUAD,:Q⁻,1,1) + +reffe = ReferenceFE(QUAD,nedelec,Float64,0) +@test reffe == ReferenceFE(QUAD,:Q⁻,1,1, Float64) + +reffe = ReferenceFE(TRI,nedelec,0) +@test reffe == ReferenceFE(TRI,:P⁻,1,1) + +reffe = ReferenceFE(HEX,nedelec,0) +@test reffe == ReferenceFE(HEX,:Q⁻,1,1) + +reffe = ReferenceFE(TET,nedelec,0) +@test reffe == ReferenceFE(TET,:P⁻,1,1) + +reffe2 = ReferenceFE(TRI,nedelec2,1) +@test reffe2 == ReferenceFE(TRI,:P,1,1) + +reffe2 = ReferenceFE(TET,nedelec2,1) +@test reffe2 == ReferenceFE(TET,:P,1,1) + +# Serendipity not implemented +@test_throws ErrorException ReferenceFE(QUAD,:S,1,1) +@test_throws ErrorException ReferenceFE(HEX, :S,1,1) + #display(face_odofs) using Gridap.Geometry diff --git a/test/ReferenceFEsTests/PDiscRefFEsTests.jl b/test/ReferenceFEsTests/PDiscRefFEsTests.jl index b86f17211..3fcfa458b 100644 --- a/test/ReferenceFEsTests/PDiscRefFEsTests.jl +++ b/test/ReferenceFEsTests/PDiscRefFEsTests.jl @@ -2,14 +2,39 @@ module PDiscRefFEsTests using Test using Gridap.TensorValues +using Gridap.Polynomials using Gridap.ReferenceFEs +nodal = true + +T = Float64 +reffe = LagrangianRefFE(T,QUAD,3,space=:P) +@test reffe == ReferenceFE(QUAD,:S,3,2; nodal) # r=2,k=D=2 + +reffe = LagrangianRefFE(T,QUAD,3,space=:P; poly_type=Bernstein) +@test reffe == ReferenceFE(QUAD,:S,3,2; poly_type=Bernstein, nodal) # r=2,k=D=2 + + +T = Float64 +reffe = LagrangianRefFE(T,HEX,2,space=:P) +@test reffe == ReferenceFE(HEX,:S,2,3,T; nodal) # r=2,k=D=3 + +reffe = LagrangianRefFE(T,HEX,2,space=:P; poly_type=Bernstein) +@test reffe == ReferenceFE(HEX,:S,2,3,T; poly_type=Bernstein, nodal) # r=2,k=D=3 + + T = VectorValue{2,Float64} reffe = LagrangianRefFE(T,QUAD,2,space=:P) @test Conformity(reffe) == L2Conformity() test_lagrangian_reference_fe(reffe) @test is_n_cube(get_polytope(reffe)) +reffe = LagrangianRefFE(T,QUAD,2,space=:P, poly_type=Bernstein,) +@test Conformity(reffe) == L2Conformity() +test_lagrangian_reference_fe(reffe) +@test is_n_cube(get_polytope(reffe)) + + T = VectorValue{2,Float64} reffe = LagrangianRefFE(T,HEX,3,space=:P) @test Conformity(reffe) == L2Conformity() diff --git a/test/ReferenceFEsTests/PolytopesTests.jl b/test/ReferenceFEsTests/PolytopesTests.jl index 5a4b73e08..1e0cc4f18 100644 --- a/test/ReferenceFEsTests/PolytopesTests.jl +++ b/test/ReferenceFEsTests/PolytopesTests.jl @@ -16,6 +16,29 @@ import Base: == struct MockVertex <: Polytope{0} end +# Abstract API +p = MockVertex() +@test_throws ErrorException get_faces(p) +@test_throws ErrorException get_dimranges(p) +@test_throws ErrorException get_dimrange(p,0) +@test_throws ErrorException get_vertex_coordinates(p) +@test_throws ErrorException get_vertex_permutations(p) +@test_throws ErrorException get_edge_tangent(p) +@test_throws ErrorException get_facet_normal(p) +@test_throws ErrorException get_facet_orientations(p) +@test_throws ErrorException p == p +@test_throws ErrorException Polytope{0}(p,0) +@test_throws ErrorException is_simplex(p) +@test_throws ErrorException is_n_cube(p) +@test_throws ErrorException simplexify(p) + +@test num_dims(p) == 0 +@test num_dims(typeof(p)) == 0 +@test num_cell_dims(p) == 0 +@test num_cell_dims(typeof(p)) == 0 +@test num_point_dims(p) == 0 +@test num_point_dims(typeof(p)) == 0 + get_faces(p::MockVertex) = [[1]] get_dimranges(p::MockVertex) = [1:1] @@ -51,7 +74,7 @@ function (==)(a::MockSegment,b::MockSegment) true end -get_vertex_coordinates(p::MockSegment) = Point{1,Float64}[(0),(1)] +get_vertex_coordinates(p::MockSegment) = Point{1,Float64}[(0,),(1,)] struct MockQuad <: Polytope{2} end diff --git a/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl b/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl index 1bd40ca7b..930d06737 100644 --- a/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl +++ b/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl @@ -21,10 +21,13 @@ order = 0 reffe = RaviartThomasRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 4 -@test get_order(get_prebasis(reffe)) == 0 +@test length(get_prebasis(reffe)) == 4 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 4 @test Conformity(reffe) == DivConformity() + +@test_warn "falling back to `change_dof=false`" RaviartThomasRefFE(et,p,order; change_dof=true, poly_type=Monomial) + p = QUAD D = num_dims(QUAD) et = Float64 @@ -32,9 +35,9 @@ order = 1 reffe = RaviartThomasRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 12 +@test length(get_prebasis(reffe)) == 12 @test num_dofs(reffe) == 12 -@test get_order(get_prebasis(reffe)) == 1 +@test get_order(get_prebasis(reffe)) == 2 prebasis = get_prebasis(reffe) dof_basis = get_dof_basis(reffe) @@ -58,18 +61,37 @@ D = num_dims(TRI) et = Float64 reffe = RaviartThomasRefFE(et,p,order) +dof_basis = get_dof_basis(reffe) +prebasis = get_prebasis(reffe) -dofs = get_dof_basis(reffe) -nodes, nf_nodes, nf_moments = get_nodes(dofs), - get_face_nodes_dofs(dofs), - get_face_moments(dofs) -nodes -nf_nodes -nf_moments +# By default, on simplices, the element uses a raw poly basis as shapefuns, and a dof prebasis +predofs = get_dof_basis(reffe).predofs +nodes, nf_nodes, nf_moments = get_nodes(predofs), get_face_nodes_dofs(predofs), get_face_moments(predofs) -### +order = 3 +p = TRI +D = num_dims(TRI) +et = Float64 +reffe = RaviartThomasRefFE(et,p,order) +prebasis = get_prebasis(reffe) +dof_basis = get_dof_basis(reffe) +v = VectorValue(3.0,0.0) +field = GenericField(x->v*x[1]) + +predofs = get_dof_basis(reffe).predofs +nodes, nf_nodes, nf_moments = get_nodes(predofs), get_face_nodes_dofs(predofs), get_face_moments(predofs) + +cache = return_cache(dof_basis,field) +r = evaluate!(cache, dof_basis, field) +test_dof_array(dof_basis,field,r) + +cache = return_cache(dof_basis,prebasis) +r = evaluate!(cache, dof_basis, prebasis) +test_dof_array(dof_basis,prebasis,r) + +### p = TET D = num_dims(TET) @@ -78,11 +100,12 @@ order = 0 reffe = RaviartThomasRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 4 +@test length(get_prebasis(reffe)) == 4 @test num_dofs(reffe) == 4 -@test get_order(get_prebasis(reffe)) == 0 +@test get_order(get_prebasis(reffe)) == 1 @test Conformity(reffe) == DivConformity() + p = TET D = num_dims(p) et = Float64 @@ -90,9 +113,9 @@ order = 2 reffe = RaviartThomasRefFE(et,p,order) test_reference_fe(reffe) -@test num_terms(get_prebasis(reffe)) == 36 +@test length(get_prebasis(reffe)) == 36 @test num_dofs(reffe) == 36 -@test get_order(get_prebasis(reffe)) == 2 +@test get_order(get_prebasis(reffe)) == 3 @test Conformity(reffe) == DivConformity() prebasis = get_prebasis(reffe) @@ -111,17 +134,27 @@ test_dof_array(dof_basis,prebasis,r) # Factory function reffe = ReferenceFE(QUAD,raviart_thomas,0) -@test num_terms(get_prebasis(reffe)) == 4 -@test get_order(get_prebasis(reffe)) == 0 +@test reffe == ReferenceFE(QUAD,:Q⁻,1,1; rotate_90=true) # r=1, k=1 +@test length(get_prebasis(reffe)) == 4 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 4 @test Conformity(reffe) == DivConformity() +@test_warn "falling back to `change_dof=false`" ReferenceFE(TET,raviart_thomas,0; poly_type=Monomial) + reffe = ReferenceFE(QUAD,raviart_thomas,Float64,0) -@test num_terms(get_prebasis(reffe)) == 4 -@test get_order(get_prebasis(reffe)) == 0 +@test reffe == ReferenceFE(QUAD,:Q⁻,1,1, Float64; rotate_90=true) # r=1, k=1 +@test length(get_prebasis(reffe)) == 4 +@test get_order(get_prebasis(reffe)) == 1 @test num_dofs(reffe) == 4 @test Conformity(reffe) == DivConformity() +reffe = ReferenceFE(HEX,raviart_thomas,0) +@test reffe == ReferenceFE(HEX,:Q⁻,1,2) # r=1, k=2 + +reffe = ReferenceFE(TET,raviart_thomas,0) +@test reffe == ReferenceFE(TET,:P⁻,1,2) # r=1, k=2 + @test Conformity(reffe,:L2) == L2Conformity() @test Conformity(reffe,:Hdiv) == DivConformity() @test Conformity(reffe,:HDiv) == DivConformity() diff --git a/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl b/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl index 6830d6835..3b5ef1b3e 100644 --- a/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl +++ b/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl @@ -5,10 +5,36 @@ using Gridap.Fields using Gridap.Polynomials using Gridap.ReferenceFEs +# Abstract API + +struct MockRefFEName <: ReferenceFEName end +@test_throws ErrorException ReferenceFE(VERTEX, MockRefFEName(), 0) + +struct MockRefFE{D} <: ReferenceFE{D} end +D = 0 +reffe = MockRefFE{D}() +@test_throws ErrorException num_dofs(reffe) +@test_throws ErrorException get_polytope(reffe) +@test_throws ErrorException get_prebasis(reffe) +@test_throws ErrorException get_dof_basis(reffe) +@test_throws ErrorException Conformity(reffe) +@test_throws ErrorException Conformity(reffe,nothing) +@test_throws ErrorException Conformity(reffe,:L2) +@test Conformity(reffe, L2Conformity()) == L2Conformity() +@test_throws ErrorException get_face_dofs(reffe) +@test_throws ErrorException get_face_own_dofs(reffe, nothing) + +@test num_dims(reffe) == D +@test num_dims(typeof(reffe)) == D +@test num_cell_dims(reffe) == D +@test num_cell_dims(typeof(reffe)) == D +@test num_point_dims(reffe) == D +@test num_point_dims(typeof(reffe)) == D + D = 2 T = Float64 order = 1 -prebasis = MonomialBasis{D}(T,order) +prebasis = MonomialBasis(Val(D),T,order) polytope = QUAD x = get_vertex_coordinates(polytope) @@ -48,4 +74,21 @@ shapefuns = get_shapefuns(reffe) @test get_own_dofs_permutations(reffe) == face_own_dofs_permutations[end] +# dof prebasis inversion from given shape functions +dofprebasis = dofs +shapefuns = prebasis # just to get nontrivial dofs change of basis matrix + +reffe = GenericRefFE{typeof(conf)}( + ndofs, polytope, dofprebasis, conf, metadata, face_dofs, shapefuns) + +test_reference_fe(reffe) + +@test get_face_own_dofs(reffe,L2Conformity()) == [[], [], [], [], [], [], [], [], [1, 2, 3, 4]] +@test get_face_own_dofs_permutations(reffe,L2Conformity()) == [[[]], [[]], [[]], [[]], [[]], [[]], [[]], [[]], [[1, 2, 3, 4]]] +@test get_own_dofs_permutations(reffe) == face_own_dofs_permutations[end] + +dofs = get_dof_basis(reffe) + +@test evaluate(dofs,shapefuns) == [1.0 0.0 0.0 0.0; 0.0 1.0 0.0 0.0; 0.0 0.0 1.0 0.0; 0.0 0.0 0.0 1.0] + end # module diff --git a/test/ReferenceFEsTests/SerendipityRefFEsTests.jl b/test/ReferenceFEsTests/SerendipityRefFEsTests.jl index f3600cd92..e0a9874a6 100644 --- a/test/ReferenceFEsTests/SerendipityRefFEsTests.jl +++ b/test/ReferenceFEsTests/SerendipityRefFEsTests.jl @@ -6,18 +6,28 @@ using Gridap.ReferenceFEs using Gridap.Polynomials using Gridap.Io +nodal = true + reffe = SerendipityRefFE(Float64,QUAD,2) +@test reffe == ReferenceFE(QUAD,:S,2,0; nodal) @test get_node_coordinates(reffe) == Point{2,Float64}[ (0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.5, 0.0), (0.5, 1.0), (0.0, 0.5), (1.0, 0.5)] @test get_face_own_nodes(reffe) == [[1], [2], [3], [4], [5], [6], [7], [8], Int[]] test_lagrangian_reference_fe(reffe) +reffer = ReferenceFE(QUAD,serendipity,2) +@test typeof(reffe) == typeof(reffer) +@test get_node_coordinates(reffe) == get_node_coordinates(reffer) +@test get_face_own_nodes(reffe) == get_face_own_nodes(reffer) + reffe = SerendipityRefFE(Float64,QUAD,4) -@test get_face_own_nodes(reffe) == [[1], [2], [3], [4], [5, 6, 7], [8, 9, 10], [11, 12, 13], [14, 15, 16], [17]] +@test reffe == ReferenceFE(QUAD,:S,4,0; nodal) +@test get_face_own_nodes(reffe) == [[1], [2], [3], [4], [5, 6, 7], [8, 9, 10], [11, 12, 13], [14, 15, 16], [17]] test_lagrangian_reference_fe(reffe) reffe = SerendipityRefFE(Float64,HEX,2) +@test reffe == ReferenceFE(HEX,:S,2,0; nodal) @test get_face_own_nodes(reffe) == [ [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], @@ -28,22 +38,41 @@ p = get_polytope(reffe) test_polytope(p) @test LagrangianRefFE(p) == HEX8 +poly_type=Polynomials.ModalC0 + order = 4 reffe = SerendipityRefFE(Float64,HEX,order) +test_lagrangian_reference_fe(reffe) +@test reffe == ReferenceFE(HEX,:S,4,0; nodal) @test get_order(reffe) == order @test get_orders(reffe) == (order,order,order) @test is_P(reffe) == false @test is_Q(reffe) == false @test is_S(reffe) == true -reffe = LagrangianRefFE(Float64,HEX,order,space=:S) +@test_throws "hierarchical" reffe = SerendipityRefFE(Float64,HEX,order; poly_type=Bernstein) + +reffe = SerendipityRefFE(Float64,HEX,order; poly_type) +test_lagrangian_reference_fe(reffe) +@test reffe == ReferenceFE(HEX,:S,4,0; poly_type, nodal) + +space=:S + +reffe = LagrangianRefFE(Float64,HEX,order; space) +test_lagrangian_reference_fe(reffe) @test get_order(reffe) == order @test get_orders(reffe) == (order,order,order) @test is_P(reffe) == false @test is_Q(reffe) == false @test is_S(reffe) == true +reffe = LagrangianRefFE(Float64,HEX,order; space, poly_type) +test_lagrangian_reference_fe(reffe) +@test reffe == ReferenceFE(HEX,:S,4,0; poly_type, nodal) + reffe = SerendipityRefFE(Float64,QUAD,(3,3)) +test_lagrangian_reference_fe(reffe) +@test reffe == ReferenceFE(QUAD,:S,3,0,Float64; nodal) @test reffe == from_dict(LagrangianRefFE,to_dict(reffe)) end # module diff --git a/test/ReferenceFEsTests/TensorProductQuadraturesTests.jl b/test/ReferenceFEsTests/TensorProductQuadraturesTests.jl index 8ca55102a..6989d5eb6 100644 --- a/test/ReferenceFEsTests/TensorProductQuadraturesTests.jl +++ b/test/ReferenceFEsTests/TensorProductQuadraturesTests.jl @@ -1,6 +1,6 @@ module TensorProductQuadraturesTests -using Gridap.ReferenceFEs +using Gridap.ReferenceFEs, Gridap.TensorValues using Test @@ -25,4 +25,12 @@ test_quadrature(quad) @test isa(get_name(quad),String) +function rational_quad(degree) + _x, w = ReferenceFEs.rational_gauss_legendre_quadrature(degree) + x = map(VectorValue{1,eltype(_x)},_x) + GenericQuadrature(x,w,"Rational Gauss-Legendre quadrature of degree $degree") +end +quads_1d = [rational_quad(degree) for degree in [4,3]] +quad = Quadrature(QUAD,tensor_product,quads_1d) + end # module diff --git a/test/ReferenceFEsTests/XiaoGimbutasQuadraturesTests.jl b/test/ReferenceFEsTests/XiaoGimbutasQuadraturesTests.jl index ab1f570e4..808438c30 100644 --- a/test/ReferenceFEsTests/XiaoGimbutasQuadraturesTests.jl +++ b/test/ReferenceFEsTests/XiaoGimbutasQuadraturesTests.jl @@ -25,8 +25,8 @@ for degree in 1:15 f1 = integrate(f,get_coordinates(quad),get_weights(quad)) f2 = integrate(f,get_coordinates(ref_quad),get_weights(ref_quad)) err = maximum(abs.(f1 .- f2))/maximum(abs.(f1)) - println("degree = ",degree," err = ",maximum(abs.(f1 .- f2))) + #println("degree = ",degree," err = ",maximum(abs.(f1 .- f2))) @test err < 1.0e-7 end -end # module \ No newline at end of file +end # module diff --git a/test/ReferenceFEsTests/runtests.jl b/test/ReferenceFEsTests/runtests.jl index c8462b964..a89b4a55b 100644 --- a/test/ReferenceFEsTests/runtests.jl +++ b/test/ReferenceFEsTests/runtests.jl @@ -16,6 +16,8 @@ using Test @testset "ReferenceFEInterfaces" begin include("ReferenceFEInterfacesTests.jl") end +@testset "GeometricDecompositions" begin include("GeometricDecompositionTests.jl") end + @testset "LagrangianRefFEs" begin include("LagrangianRefFEsTests.jl") end @testset "CLagrangianRefFEs" begin include("CLagrangianRefFEsTests.jl") end @@ -44,6 +46,14 @@ using Test @testset "ModalC0RefFEs" begin include("ModalC0RefFEsTests.jl") end +#@testset "HHJRefFEs" begin include("HHJRefFEsTests.jl") end + +@testset "BDMRefFEs" begin include("BDMRefFEsTests.jl") end + +@testset "ModalScalarRefFEs" begin include("ModalScalarRefFEsTests.jl") end + +@testset "CrouzeixRaviartFEs" begin include("CrouzeixRaviartFEsTests.jl") end + @testset "BubbleRefFEs" begin include("BubbleRefFEsTests.jl") end end # module diff --git a/test/TensorValuesTests/IndexingTests.jl b/test/TensorValuesTests/IndexingTests.jl index 2d2a5d5bd..181764128 100644 --- a/test/TensorValuesTests/IndexingTests.jl +++ b/test/TensorValuesTests/IndexingTests.jl @@ -4,6 +4,9 @@ using Test using Gridap.TensorValues using StaticArrays +@test Base.IndexStyle(MultiValue) == IndexCartesian() +@test Base.IndexStyle(VectorValue()) == IndexCartesian() + a = (3,4,5,1) v = VectorValue{4}(a) @@ -18,6 +21,8 @@ v = VectorValue{4}(a) @test length(v) == 4 @test lastindex(v) == length(v) @test v[end] == a[end] +@test_throws BoundsError v[end+1] +@test_throws BoundsError v[0] for (k,i) in enumerate(eachindex(v)) @test v[i] == a[k] @@ -32,6 +37,8 @@ t = TensorValue{2}(a) @test length(t) == 4 @test lastindex(t) == length(t) @test t[end] == a[end] +@test_throws BoundsError t[end+1] +@test_throws BoundsError t[0,0] for (k,i) in enumerate(eachindex(t)) @test t[i] == a[k] @@ -47,14 +54,23 @@ end s = SymTensorValue{2}(11,21,22) q = SymTracelessTensorValue{2}(11,21) +K = SkewSymTensorValue{2}(21) t = TensorValue(convert(SMatrix{2,2,Int},s)) p = TensorValue(convert(SMatrix{2,2,Int},q)) +r = TensorValue(convert(SMatrix{2,2,Int},K)) -@test size(s) == size(q) == (2,2) -@test length(s) == length(q) == 4 -@test lastindex(s) == lastindex(q) == length(s) +@test size(s) == size(q) == size(K) == (2,2) +@test length(s) == length(q) == length(K) == 4 +@test lastindex(s) == lastindex(q) == lastindex(K) == length(s) @test s[end] == 22 @test q[end] == -11 +@test K[begin] == K[end] == 0 +@test_throws BoundsError s[end+1] +@test_throws BoundsError s[0,0] +@test_throws BoundsError q[end+1] +@test_throws BoundsError q[0,0] +@test_throws BoundsError K[end+1] +@test_throws BoundsError K[0,0] for (k,i) in enumerate(eachindex(t)) @test s[i] == t[k] @@ -62,9 +78,13 @@ end for (k,i) in enumerate(eachindex(p)) @test q[i] == p[k] end +for (k,i) in enumerate(eachindex(r)) + @test K[i] == r[k] +end @test s[2,1] == q[2,1] == 21 -@test s[2] == q[2] == 21 +@test s[2] == q[2] == -K[2] == 21 +@test s[3] == q[3] == K[3] == 21 @test q[1] == -q[4] for (k,si) in enumerate(t) @@ -73,6 +93,9 @@ end for (k,qi) in enumerate(p) @test qi == q[k] end +for (k,Ki) in enumerate(r) + @test Ki == K[k] +end # Index and size APIs v = SMatrix{2,3}(1:6...) diff --git a/test/TensorValuesTests/OperationsTests.jl b/test/TensorValuesTests/OperationsTests.jl index 86c01dcd7..02619c99f 100644 --- a/test/TensorValuesTests/OperationsTests.jl +++ b/test/TensorValuesTests/OperationsTests.jl @@ -8,6 +8,16 @@ using LinearAlgebra using Gridap.TensorValues: _eltype # Comparison +a0 = 0 +a1 = 1 +b = VectorValue(1,3,3) + +@test (a0 < b) == true +@test (a0 <= b) == true +@test (a0 == b) == false +@test (a1 < b) == false +@test (a1 <= b) == true + a = VectorValue(1,2,3) b = VectorValue(1,3,3) @@ -45,8 +55,12 @@ b = VectorValue(2,1,6) @test [a,a] == [a,a] @test [a,a] ≈ [a,a] -c = TensorValue(1,2,3,4) - +c = TensorValue(1:9...) +@test !(a==c) +@test_throws DimensionMismatch !(a≈c) +@test !([a,a]==[c,c]) +@test !([a,a,a]≈[c,c]) +@test_throws DimensionMismatch !([a,a]≈[c,c]) @test_throws ErrorException (a < c) @test_throws ErrorException (a <= c) @@ -112,6 +126,11 @@ c = a - b r = TensorValue(-4, -4, -4, -4) @test c==r +b = TensorValue(5.,6,7,8) # promote_rule test +c = a - b +r = TensorValue(-4.,-4,-4,-4) +@test c===r + a = SymTensorValue(1,2,3) b = SymTensorValue(5,6,7) @@ -127,6 +146,11 @@ c = b - a r = SymTensorValue(4,4,4) @test c==r +b = SymTensorValue(5.,6,7) +c = a + b +r = SymTensorValue(6.,8,10) +@test c===r + a = TensorValue(1,2,3,4) b = SymTensorValue(5,6,7) @@ -159,6 +183,11 @@ c = a - b r = SymTracelessTensorValue(-4,-4) @test c==r +b = SymTracelessTensorValue(5.,6) +c = a + b +r = SymTracelessTensorValue(6.,8) +@test c===r + a = SymTensorValue(1,2,3) b = SymTracelessTensorValue(5,6) @@ -176,6 +205,61 @@ c = b - a r = SymTensorValue(4,4,-8) @test c==r +a = SkewSymTensorValue(1,2,3) +b = SkewSymTensorValue(4,5,6) + +c = -a +r = SkewSymTensorValue(-1,-2,-3) +@test c==r + +c = a + b +r = SkewSymTensorValue(5,7,9) +@test c==r + +c = a - b +r = SkewSymTensorValue(-3,-3,-3) +@test c==r + +b = SkewSymTensorValue(4.,5,6) +c = a + b +r = SkewSymTensorValue(5.,7,9) +@test c===r + +a = SkewSymTensorValue(1,2,3) +b = SymTracelessTensorValue(1:5...) + +c = a + b +d = b + a +r = TensorValue(1, 1, 1, 3, 4, 2, 5, 8, -5) +@test c==r +@test d==r + +c = a - b +r = TensorValue(-1,-3,-5,-1,-4,-8,-1,-2,5) +@test c==r + +c = b - a +r = TensorValue(1,3,5,1,4,8,1,2,-5) +@test c==r + +a = SkewSymTensorValue(1,2,3) +b = SymTensorValue(1:6...) + +c = a + b +d = b + a +r = TensorValue(1, 1, 1, 3, 4, 2, 5, 8, 6) +@test c==r +@test d==r + +c = a - b +r = TensorValue(-1,-3,-5,-1,-4,-8,-1,-2,-6) +@test c==r + +c = b - a +r = TensorValue(1,3,5,1,4,8,1,2,6) +@test c==r + + a = SymTensorValue(5,6,7) b = TensorValue(1,2,3,4) @@ -210,10 +294,49 @@ c = b - a r = TensorValue(-4,-4,-3,9) @test c==r +a = SkewSymTensorValue(1,2,3) +b = TensorValue(1:9...) + +c = a + b +d = b + a +r = TensorValue(1, 1, 1, 5, 5, 3, 9, 11, 9) +@test c==r +@test d==r + +c = a - b +r = TensorValue(-1,-3,-5,-3,-5,-9,-5,-5,-9) +@test c==r + +c = b - a +r = TensorValue(1,3,5,3,5,9,5,5,9) +@test c==r + +a = ThirdOrderTensorValue(1:8...) +b = ThirdOrderTensorValue(9:16...) + +c = -a +r=ThirdOrderTensorValue(-1,-2,-3,-4,-5,-6,-7,-8) +@test c==r + +c = a + b +r=ThirdOrderTensorValue(10,12,14,16,18,20,22,24) +@test c==r + +c = a - b +r=ThirdOrderTensorValue(-8,-8,-8,-8,-8,-8,-8,-8) +@test c==r + +b = ThirdOrderTensorValue(9.,10:16...) +c = a + b +r = ThirdOrderTensorValue(10.,12,14,16,18,20,22,24) +@test c===r + + v = VectorValue(1,2) t = TensorValue(1,2,3,4) s = SymTensorValue(1,2,3) q = SymTracelessTensorValue(1,2) +k = SkewSymTensorValue(1) r = ThirdOrderTensorValue(1:8...) f = SymFourthOrderTensorValue(1:9...) @@ -226,6 +349,9 @@ f = SymFourthOrderTensorValue(1:9...) @test_throws ErrorException v+q @test_throws ErrorException v-q @test_throws ErrorException q-v +@test_throws ErrorException v-k +@test_throws ErrorException k-v + @test_throws ErrorException q+0 @test_throws ErrorException 0+q @test_throws ErrorException 0-q @@ -246,6 +372,10 @@ f = SymFourthOrderTensorValue(1:9...) @test_throws ErrorException q*q @test_throws ErrorException v/q @test_throws ErrorException q/v +@test_throws ErrorException v*k +@test_throws ErrorException k*v +@test_throws ErrorException v/k +@test_throws ErrorException k/v # Matrix Division @@ -259,11 +389,15 @@ st = one(SymTensorValue{3,Int}) c = st\a @test c == a +@test_throws ErrorException one(SkewSymTensorValue{3,Int}) +@test_throws ErrorException one(MultiValue) + # Operations by a scalar t = TensorValue(1,2,3,4,5,6,7,8,9) st = SymTensorValue(1,2,3,5,6,9) qt = SymTracelessTensorValue(1,2,3,5,6) +sk = SkewSymTensorValue(1,2,3) s4ot = one(SymFourthOrderTensorValue{2,Int}) a = VectorValue(1,2,3) @@ -348,6 +482,21 @@ c = qt / 2 r = SymTracelessTensorValue(.5,1,1.5,2.5,3) @test c == r +c = 2 * sk +@test isa(c,SkewSymTensorValue{3}) +r = SkewSymTensorValue(2,4,6) +@test c == r + +c = sk * 2 +@test isa(c,SkewSymTensorValue{3}) +r = SkewSymTensorValue(2,4,6) +@test c == r + +c = sk / 2 +@test isa(c,SkewSymTensorValue{3}) +r = SkewSymTensorValue(.5,1,1.5) +@test c == r + c = 2 * s4ot @test isa(c,SymFourthOrderTensorValue{2}) r = SymFourthOrderTensorValue(2,0,0, 0,1,0, 0,0,2) @@ -379,6 +528,8 @@ st = SymTensorValue(1,2,3,5,6,9) st2 = SymTensorValue(9,6,5,3,2,1) qt = SymTracelessTensorValue(1,2,3,5,6) qt2 = SymTracelessTensorValue(9,6,5,3,2) +sk = SkewSymTensorValue(1,2,3) +sk2 = SkewSymTensorValue(3,2,1) c = a ⋅ b @test isa(c,Int) @@ -399,6 +550,11 @@ c = qt ⋅ a r = VectorValue(14,30,-3) @test c == r +c = sk ⋅ a +@test isa(c,VectorValue{3,Int}) +r = VectorValue(8,8,-8) +@test c == r + c = s ⋅ t @test isa(c,TensorValue{3,3,Int}) r = TensorValue(38,24,18,98,69,48,158,114,78) @@ -414,6 +570,11 @@ c = qt ⋅ qt2 r = TensorValue(36, 78, 33, 18, 39, 24, -27, -52, 99) @test c == r +c = sk ⋅ sk2 +@test isa(c,TensorValue{3,3,Int}) +r = TensorValue(-7, -6, 9, -2, -6, -6, 1, -2, -7) +@test c == r + c = st ⋅ qt2 @test isa(c,TensorValue{3,3,Int}) r = TensorValue(36, 78, 108, 18, 39, 54, -27, -52, -81) @@ -424,6 +585,16 @@ c = qt2 ⋅ st r = TensorValue(36, 18, -27, 78, 39, -52, 108, 54, -81) @test c == r +c = st ⋅ sk2 +@test isa(c,TensorValue{3,3,Int}) +r = TensorValue(-12, -27, -36, 0, 0, 0, 4, 9, 12) +@test c == r + +c = sk2 ⋅ st +@test isa(c,TensorValue{3,3,Int}) +r = TensorValue(12, 0, -4, 27, 0, -9, 36, 0, -12) +@test c == r + c = a ⋅ st @test isa(c,VectorValue{3,Int}) r = VectorValue(14,30,42) @@ -434,6 +605,11 @@ c = a ⋅ qt r = VectorValue(14,30,-3) @test c == r +c = a ⋅ sk +@test isa(c,VectorValue{3,Int}) +r = VectorValue(-8,-8,8) +@test c == r + a1 = VectorValue(1,0) b1 = VectorValue(1,2) @@ -562,6 +738,9 @@ e = VectorValue(10,20) k = TensorValue(1,2,3,4) @test tr(outer(e,k)) == VectorValue(50,110) +a = TensorValue{0,0}() +@test_throws ErrorException outer(a,a) # FourthOrderTensorValue isn't implemented + # Cross product a = VectorValue(1,2,3) @@ -597,14 +776,38 @@ c = det(t) c = inv(t) @test isa(c,TensorValue{3}) +st = SymTensorValue(1:3...) +@test det(st) == det(TensorValue(get_array(st))) +@test inv(st) ≈ inv(TensorValue(get_array(st))) + st = SymTensorValue(9,8,7,5,4,1) @test det(st) == det(TensorValue(get_array(st))) @test inv(st) ≈ inv(TensorValue(get_array(st))) +qk = SkewSymTensorValue{1,Int}() +@test det(qk) == det(TensorValue(get_array(qk))) +@test inv(qk) ≈ inv(TensorValue(get_array(qk))) + qt = SymTracelessTensorValue(9,8,7,5,4) @test det(qt) == det(TensorValue(get_array(qt))) @test inv(qt) ≈ inv(TensorValue(get_array(qt))) +sk = SkewSymTensorValue{1,Int}() +@test det(sk) == det(TensorValue(get_array(sk))) +@test inv(sk) ≈ inv(TensorValue(get_array(sk))) + +sk = SkewSymTensorValue(2.) +@test det(sk) == det(TensorValue(get_array(sk))) +@test inv(sk) ≈ inv(TensorValue(get_array(sk))) + +sk = SkewSymTensorValue(1. : 3. ...) +@test det(sk) == det(TensorValue(get_array(sk))) +@test inv(sk) == SkewSymTensorValue(Inf,Inf,Inf) + +sk = SkewSymTensorValue(1:6...) +@test det(sk) == det(TensorValue(get_array(sk))) +@test inv(sk) ≈ inv(TensorValue(get_array(sk))) + t = TensorValue(10) @test det(t) == 10 @test inv(t) == TensorValue(1/10) @@ -622,6 +825,9 @@ q = SymTracelessTensorValue(1,2) @test det(q) == det(TensorValue(get_array(q))) @test inv(q) == SymTracelessTensorValue(inv(get_array(q))) +sk = SkewSymTensorValue(1,2,3) +@test det(sk) == 0 + t = TensorValue{2,3}(1:6...) @test_throws ErrorException det(t) @test_throws ErrorException inv(t) @@ -680,6 +886,9 @@ st = SymTensorValue(1,2,3,5,6,9) qt = SymTracelessTensorValue(1,2,3,5,6) @test meas(qt) == meas(TensorValue(get_array(qt))) +sk = SkewSymTensorValue(1,2,3) +@test meas(sk) == meas(TensorValue(get_array(sk))) + v = TensorValue{1,2}(10,20) @test meas(v) == sqrt(500) @@ -738,11 +947,31 @@ st = SymTensorValue(1,2,3,5,6,9) qt = SymTracelessTensorValue(1,2,3,5,6) @test tr(qt) == tr(TensorValue(get_array(qt))) +sk = SkewSymTensorValue(1,2,3) +@test tr(sk) == tr(TensorValue(get_array(sk))) + +t23 = TensorValue{2,3}(1:6...) +@test_throws ArgumentError tr(t23) + + @test get_array(symmetric_part(t)) == get_array(TensorValue(1.0, 3.0, 5.0, 3.0, 5.0, 7.0, 5.0, 7.0, 9.0)) @test symmetric_part(st) == symmetric_part(TensorValue(get_array(st))) @test symmetric_part(st) === st @test symmetric_part(qt) == symmetric_part(TensorValue(get_array(qt))) @test symmetric_part(qt) === qt +ss = symmetric_part(sk) +@test zero(ss) == ss == symmetric_part(TensorValue(get_array(sk))) + +@test skew_symmetric_part(sk) == SkewSymTensorValue(get_array(sk)) +@test sk === skew_symmetric_part(sk) +skt = .5(t - t') +@test skew_symmetric_part(skt) == SkewSymTensorValue(get_array(skt)) +skt = .5(st - st') +sk = skew_symmetric_part(skt) +@test zero(sk) == sk == SkewSymTensorValue(get_array(skt)) +skt = .5(qt - qt') +sk = skew_symmetric_part(skt) +@test zero(sk) == sk == SkewSymTensorValue(get_array(skt)) a = TensorValue(1,2,3,4) b = a' @@ -769,18 +998,32 @@ sb = sa' @test sb == SymTensorValue(1,2,3,5,6,9) @test sa⋅sb == TensorValue(get_array(sa))⋅TensorValue(get_array(sb)) -sa = SymTensorValue(1,2,3,5,6,9) -sb = sa' -@test transpose(sa) == sb -@test sb == SymTensorValue(1,2,3,5,6,9) +sa = SymTensorValue(1+im,2,3,5,6,9) +sb = SymTensorValue(1-im,2,3,5,6,9) +@test sa' == sb +@test adjoint(sa) == sb +@test transpose(sa) === sa @test sa⋅sb == TensorValue(get_array(sa))⋅TensorValue(get_array(sb)) -sa = SymTracelessTensorValue(1,2,3,5,6) -sb = sa' +sa = SymTracelessTensorValue(1+im,2,3,5,6) +sb = SymTracelessTensorValue(1-im,2,3,5,6) +@test sa' == sb +@test adjoint(sa) == sb +@test transpose(sa) === sa +@test sa⋅sb == TensorValue(get_array(sa))⋅TensorValue(get_array(sb)) + +sa = SkewSymTensorValue(1+im,2,3) +sb = SkewSymTensorValue(-1+im,-2,-3) +@test sa' == sb @test adjoint(sa) == sb -@test sb == SymTracelessTensorValue(1,2,3,5,6) +@test transpose(sa) === -sa @test sa⋅sb == TensorValue(get_array(sa))⋅TensorValue(get_array(sb)) +u = VectorValue() +v = VectorValue{0,ComplexF64}() +@test norm(u) == 0 +@test norm(v) == 0. + 0im + u = VectorValue(1.0,2.0) v = VectorValue(2.0,3.0) @test dot(u,v) ≈ inner(u,v) @@ -869,6 +1112,16 @@ odot_contraction_array = 1*a[:,1,1] + 2*a[:,1,2] + 3*a[:,1,3] + 2*a[:,2,1] + 4*a[:,2,2] + 5*a[:,2,3] + 3*a[:,3,1] + 5*a[:,3,2] + (-5)*a[:,3,3] @test odot_contraction == odot_contraction_array +a = reshape(Vector(1:27),(3,3,3)) +a_tensor = ThirdOrderTensorValue(a...) +b_tensor = SkewSymTensorValue((1:3)...) +b = Matrix(get_array(b_tensor)) +odot_contraction = Vector(get_array(a_tensor ⋅² b_tensor)) +odot_contraction_array = 0*a[:,1,1] + 1*a[:,1,2] + 2*a[:,1,3] - 1*a[:,2,1] + + 0*a[:,2,2] + 3*a[:,2,3] - 2*a[:,3,1] - 3*a[:,3,2] + 0*a[:,3,3] +@test odot_contraction == odot_contraction_array + + # double Contractions w/ products v = VectorValue(1:2...) @@ -1029,6 +1282,32 @@ for ci in CartesianIndices((3,3)) @test t2_dot_t1[ci] == sum(t2[ci[1],i]*t1[i,ci[2]] for i in 1:3) end +# product of congruence + +t = TensorValue{2,3}(1:6...) +t2 = TensorValue{2,2}(1:4...) +s = SymTensorValue{2}(1,2,3) +st = SymTracelessTensorValue{2}(1,2) +sk = SkewSymTensorValue{2}(1) + +r = congruent_prod(t2,t) +@test r isa TensorValue{3,3} +@test r == t'⋅t2⋅t + +r = congruent_prod(s,t) +@test r isa SymTensorValue{3} +@test get_array(r) == get_array(t'⋅s⋅t) + +r = congruent_prod(sk,t) +@test r isa SkewSymTensorValue{3} +@test get_array(r) == get_array(t'⋅sk⋅t) + +r = congruent_prod(st,t) +@test r isa SymTensorValue{3} +@test get_array(r) == get_array(t'⋅st⋅t) + +@test_throws ErrorException congruent_prod(t,t2) # incompatible size + # Complex a = 1.0 + 3.0*im @@ -1071,6 +1350,13 @@ v2 = SymTracelessTensorValue(1, 1) v2 = SymTracelessTensorValue(1-1im, 1-1im) @test conj(v1) == v2 && eltype(conj(v1)) == eltype(v2) +v1 = SkewSymTensorValue(1+1im) +v2 = SkewSymTensorValue(1) +@test real(v1) == v2 && eltype(real(v1)) == eltype(v2) +@test imag(v1) == v2 && eltype(imag(v1)) == eltype(v2) +v2 = SkewSymTensorValue(1-1im) +@test conj(v1) == v2 && eltype(conj(v1)) == eltype(v2) + #_eltype @test _eltype(real,Tuple{},VectorValue(1.0,2.0))==Union{} @test _eltype(real,Tuple{},VectorValue(1.0,2.0),VectorValue(2.0,3.0),VectorValue(3.0,4.0))==Union{} @@ -1109,7 +1395,8 @@ c = a .* b @test isa(c,SymTensorValue) @test c.data == map(*,a.data,b.data) -@test diag(a) == VectorValue(1,-1) +a = SkewSymTensorValue(1,2,3) +@test diag(a) == zero(VectorValue(1:3...)) # Operation involving empty tensors (check type promotion) @@ -1120,6 +1407,7 @@ t20 = TensorValue{2,0,Float32}() t02 = TensorValue{0,2,Float32}() t23 = TensorValue{2,3,Float32}(0,0,0,0,0,0) st0 = SymTracelessTensorValue{0,ComplexF64}() +sk0 = SkewSymTensorValue{0,ComplexF64}() t210= ThirdOrderTensorValue{2,1,0,ComplexF16}() t012= ThirdOrderTensorValue{0,1,2,ComplexF16}() t123= ThirdOrderTensorValue{1,2,3,Float64}(0,0,0,0,0,0) @@ -1129,6 +1417,8 @@ f0 = SymFourthOrderTensorValue{0,ComplexF16}() @test t20 ⋅ v0 === VectorValue{2,Float64}(0,0) @test v0 ⋅ st0 === VectorValue{0,ComplexF64}() @test st0 ⋅ v0 === VectorValue{0,ComplexF64}() +@test v0 ⋅ sk0 === VectorValue{0,ComplexF64}() +@test sk0 ⋅ v0 === VectorValue{0,ComplexF64}() @test t20 ⋅ t02 === TensorValue{2,2,Float32}(0,0,0,0) @test t02 ⋅ t20 === TensorValue{0,0,Float32}() @test v0 ⋅ t012 === TensorValue{1,2,ComplexF64}(0,0) @@ -1138,17 +1428,23 @@ f0 = SymFourthOrderTensorValue{0,ComplexF16}() @test t012 ⋅ t23 === ThirdOrderTensorValue{0,1,3,ComplexF32}() @test t02 ⋅ t210 === ThirdOrderTensorValue{0,1,0,ComplexF32}() +@test sk0 ⊙ sk0 === zero(ComplexF64) @test st0 ⊙ st0 === zero(ComplexF64) @test st0 ⊙ t00 === zero(ComplexF64) @test t00 ⊙ st0 === zero(ComplexF64) +@test st0 ⊙ sk0 === zero(ComplexF64) +@test sk0 ⊙ st0 === zero(ComplexF64) +@test sk0 ⊙ t00 === zero(ComplexF64) +@test t00 ⊙ sk0 === zero(ComplexF64) @test t00 ⊙ t00 === zero(Float32) @test t00 ⊙ f0 === SymTensorValue{0,ComplexF32,0}() @test f0 ⊙ t00 === SymTensorValue{0,ComplexF32,0}() -@test f0 ⊙ f0 === SymFourthOrderTensorValue{0, ComplexF16}() +@test f0 ⊙ f0 === zero(ComplexF16) t12 = TensorValue{1,2,Float32}(0,0) t10 = TensorValue{1,0,Float32}() st2 = SymTracelessTensorValue{2,Float32}(0,0) +sk2 = SkewSymTensorValue{2,Float32}(0) t022 = ThirdOrderTensorValue{0,2,2,ComplexF16}() t220 = ThirdOrderTensorValue{2,2,0,ComplexF16}() t200 = ThirdOrderTensorValue{2,0,0,Float64}() @@ -1159,12 +1455,16 @@ t002 = ThirdOrderTensorValue{0,0,2,Float64}() @test f0 ⋅² t00 === SymTensorValue{0,ComplexF32,0}() @test st0 ⋅² f0 === SymTensorValue{0,ComplexF64,0}() @test f0 ⋅² st0 === SymTensorValue{0,ComplexF64,0}() +@test sk0 ⋅² f0 === SymTensorValue{0,ComplexF64,0}() +@test f0 ⋅² sk0 === SymTensorValue{0,ComplexF64,0}() @test t012 ⋅² t12 === VectorValue{0,ComplexF32}() @test t210 ⋅² t10 === VectorValue{2,ComplexF32}(0,0) @test t200 ⋅² st0 === VectorValue{2,ComplexF64}(0,0) @test t022 ⋅² st2 === VectorValue{0,ComplexF32}() +@test t022 ⋅² sk2 === VectorValue{0,ComplexF32}() @test st0 ⋅² t002 === VectorValue{2,ComplexF64}(0,0) @test st2 ⋅² t220 === VectorValue{0,ComplexF32}() +@test sk2 ⋅² t220 === VectorValue{0,ComplexF32}() @test t022 ⋅² t220 === TensorValue{0,0,ComplexF16}() @test t200 ⋅² t002 === TensorValue{2,2,Float64}(0,0,0,0) @test f0 ⋅² f0 === SymFourthOrderTensorValue{0,ComplexF16}() @@ -1183,6 +1483,9 @@ t01c = TensorValue{0,1,ComplexF32}() @test conj(st0) === st0 @test real(st0) === SymTracelessTensorValue{0,Float64}() @test imag(st0) === SymTracelessTensorValue{0,Float64}() +@test conj(sk0) === sk0 +@test real(sk0) === SkewSymTensorValue{0,Float64}() +@test imag(sk0) === SkewSymTensorValue{0,Float64}() @test tr(t00) === zero(Float32) @test tr(t220) === VectorValue{0,ComplexF16}() @test adjoint(t00) === t00 diff --git a/test/TensorValuesTests/TypesTests.jl b/test/TensorValuesTests/TypesTests.jl index 71c67d3ce..250196d6b 100644 --- a/test/TensorValuesTests/TypesTests.jl +++ b/test/TensorValuesTests/TypesTests.jl @@ -11,6 +11,7 @@ a = SMatrix{2,2}(1,2,3,4) t = TensorValue(a) @test isa(t,TensorValue{2,2,Int}) @test convert(SMatrix{2,2,Int},t) == [1 3;2 4] +@test_throws MethodError convert(SMatrix{1,2,Int},t) @test convert(SMatrix{2,2,Int},t) === SArray(t) @test convert(MMatrix{2,2,Int},t) == MArray(t) @test t === MultiValue(a) @@ -66,7 +67,9 @@ t = TensorValue{2,2,Int}(1,2.0,3,4) a = [11.0 21.0; 12.0 22.0] @test isa(a,AbstractArray{Float64,2}) t = convert(TensorValue{2,2,Float64},a) -@test t == TensorValue{2,2,Float64,3}(11.0, 12.0, 21.0, 22.0) +@test t == TensorValue{2,2,Float64,3}(11, 12, 21, 22) +t = convert(TensorValue{2,2,Float32},a) +@test t == TensorValue{2,2,Float32,3}(11, 12, 21, 22) m = convert(MMatrix{2,2,Float64},t) @test m == MMatrix{2}(11.0, 12.0, 21.0, 22.0) u = convert(NTuple{4,Float64},t) @@ -85,6 +88,7 @@ t = TensorValues.tensor_from_rows(v...) s = SymTensorValue( (11,21,22) ) @test isa(s,SymTensorValue{2,Int}) @test convert(SMatrix{2,2,Int},s) == [11 21;21 22] +@test_throws MethodError convert(SMatrix{1,2,Int},s) s = SymTensorValue(11,21,22) @test isa(s,SymTensorValue{2,Int}) @@ -131,7 +135,7 @@ a = [11.0 21.0; NaN 22.0] @test convert(SymTensorValue{2,Float64},a) == SymTensorValue{2,Float64,3}(11.0, 21.0, 22.0) # Constructors (SymTracelessTensorValue) -q_none = SymTracelessTensorValue{0, Int64, 0}() +q_none = SymTracelessTensorValue{0, Int, 0}() q = SymTracelessTensorValue() @test q == q_none q = SymTracelessTensorValue{0}() @@ -156,6 +160,7 @@ q = rand(SymTracelessTensorValue{0,Int}) q = SymTracelessTensorValue( (11,21) ) @test isa(q,SymTracelessTensorValue{2,Int}) @test convert(SMatrix{2,2,Int},q) == [11 21;21 -11] +@test_throws MethodError convert(SMatrix{1,2,Int},q) q = SymTracelessTensorValue{2,Int,3}( (11,21) ) @test isa(q,SymTracelessTensorValue{2,Int,3}) @@ -222,6 +227,108 @@ m = convert(MMatrix{2,2,Float64},t) u = convert(NTuple{3,Float64},t) @test u == tuple(11.0, 21.0, -11.0) +# Constructors (SkewSymTensorValue) + +q_none = SkewSymTensorValue{0, Int, 0}() +q = SkewSymTensorValue() +@test q == q_none +q = SkewSymTensorValue{0}() +@test q == q_none +q = SkewSymTensorValue(Tuple{}()) +@test q == q_none +q = SkewSymTensorValue{0}(Tuple{}()) +@test q == q_none + +q_zero = SkewSymTensorValue{1,Int}(NTuple{0,Int}()) +q = SkewSymTensorValue{1}() +@test q == q_zero +q = SkewSymTensorValue{1}(Tuple{}()) +@test q == q_zero +q = SkewSymTensorValue{1,Int}(Tuple{}()) +@test q == q_zero + +q = rand(SkewSymTensorValue{0,Int}) +@test eltype(q) == Int +@test eltype(typeof(q)) == Int + +q = SkewSymTensorValue( (12,) ) +@test isa(q,SkewSymTensorValue{2,Int}) +@test convert(SMatrix{2,2,Int},q) == [0 12;-12 0] +@test_throws MethodError convert(SMatrix{1,2,Int},q) + +q = SkewSymTensorValue{2,Int,1}( (12,) ) +@test isa(q,SkewSymTensorValue{2,Int,1}) +@test convert(SMatrix{2,2,Int},q) == [0 12;-12 0] + +q = SkewSymTensorValue{2,Int,1}(12) +@test isa(q,SkewSymTensorValue{2,Int,1}) +@test convert(SMatrix{2,2,Int},q) == [0 12;-12 0] + +q = SkewSymTensorValue( (12.,13,23) ) +@test isa(q,SkewSymTensorValue{3,Float64}) +@test convert(SMatrix{3,3,Int},q) == [0 12 13; -12 0 23; -13 -23 0] + +q = SkewSymTensorValue(12) +@test isa(q,SkewSymTensorValue{2,Int}) +@test convert(SMatrix{2,2,Int},q) == [0 12;-12 0] + +q = SkewSymTensorValue{3}( (12.,13,23) ) +@test isa(q,SkewSymTensorValue{3,Float64}) +@test convert(SMatrix{3,3,Int},q) == [0 12 13; -12 0 23; -13 -23 0] + +q = SkewSymTensorValue{2}( 12 ) +@test isa(q,SkewSymTensorValue{2,Int}) +@test convert(SMatrix{2,2,Int},q) == [0 12;-12 0] + +q = SkewSymTensorValue{3,Int}( (12.,13,23) ) +@test isa(q,SkewSymTensorValue{3,Int}) +@test convert(SMatrix{3,3,Int},q) == [0 12 13; -12 0 23; -13 -23 0] + +q = SkewSymTensorValue{2,Float64}(12) +@test isa(q,SkewSymTensorValue{2,Float64}) +@test convert(SMatrix{2,2,Int},q) == [0 12;-12 0] + +q = SkewSymTensorValue{3,Int,3}( (12.,13,23) ) +@test isa(q,SkewSymTensorValue{3,Int,3}) +@test convert(SMatrix{3,3,Int},q) == [0 12 13; -12 0 23; -13 -23 0] + +q = SkewSymTensorValue{0,Int}( () ) +@test isa(q,SkewSymTensorValue{0,Int}) +@test convert(SMatrix{0,0,Int},q) == Array{Any,2}(undef,0,0) + +q = SkewSymTensorValue{0,Int}() +@test isa(q,SkewSymTensorValue{0,Int}) +@test convert(SMatrix{0,0,Int},q) == Array{Any,2}(undef,0,0) + +q = SkewSymTensorValue{1,Int}( () ) +@test isa(q,SkewSymTensorValue{1,Int}) +@test convert(SMatrix{1,1,Int},q) == zeros(Int,1,1) + +q = SkewSymTensorValue{1,Int}() +@test isa(q,SkewSymTensorValue{1,Int}) +@test convert(SMatrix{1,1,Int},q) == zeros(Int,1,1) + +q = SkewSymTensorValue(12.0) +@test isa(q,SkewSymTensorValue{2,Float64}) +@test convert(SMatrix{2,2,Float64},q) == [0. 12.;-12. 0.] + +q = SkewSymTensorValue{2}(12.0) +@test isa(q,SkewSymTensorValue{2,Float64}) +@test convert(SMatrix{2,2,Float64},q) == [0. 12.;-12. 0.] + +q = SkewSymTensorValue{2,Int}(12.) +@test isa(q,SkewSymTensorValue{2,Int}) +@test convert(SMatrix{2,2,Float64},q) == [0 12.;-12. 0] + +a = [NaN 12.0; NaN NaN] +@test isa(a,AbstractArray{Float64,2}) +t = convert(SkewSymTensorValue{2,Float64},a) +@test t == SkewSymTensorValue{2,Float64,3}(12.0) +m = convert(MMatrix{2,2,Float64},t) +@test m == MMatrix{2}(0, -12.0, 12.0, 0) +u = convert(NTuple{1,Float64},t) +@test u == tuple(12.0) + # Constructors (SymFourthOrderTensorValue) s = SymFourthOrderTensorValue( (1111,1121,1122, 2111,2121,2122, 2211,2221,2222) ) @@ -274,6 +381,7 @@ a = SVector(1) g = VectorValue(a) @test isa(g,VectorValue{1,Int}) @test convert(SVector{1,Int},g) == [1,] +@test_throws MethodError convert(SVector{2,Int},g) @test convert(SVector{1,Int},g) === SArray(g) @test convert(MVector{1,Int},g) == MArray(g) @test g === MultiValue(a) @@ -599,7 +707,9 @@ m = mutable(v) @test isa(m,MArray) v = SymFourthOrderTensorValue{2}(1:9...) -@test_throws ErrorException mutable(v) #notimplemented +m = mutable(v) +@test m == get_array(v) +@test isa(m,MArray) M = Mutable(VectorValue{3,Int}) @test M == MVector{3,Int} @@ -608,48 +718,98 @@ v = VectorValue(m) @test isa(v,VectorValue{3,Int}) M2 = Mutable(v) @test M == M2 +@test M[ v ] == M[ m ] # convert test M = Mutable(TensorValue{3,3,Int}) -@test M == MMatrix{3,3,Int} +@test M == MMatrix{3,3,Int,9} m = zero(M) v = TensorValue(m) @test isa(v,TensorValue{3,3,Int}) M2 = Mutable(v) @test M == M2 +@test M[ v ] == M[ m ] M = Mutable(SymTensorValue{3,Int}) -@test M == MMatrix{3,3,Int} +@test M == MMatrix{3,3,Int,9} m = zero(M) v = SymTensorValue(m) @test isa(v,SymTensorValue{3,Int}) M2 = Mutable(v) @test M == M2 +@test M[ v ] == M[ m ] M = Mutable(SymTracelessTensorValue{3,Int}) -@test M == MMatrix{3,3,Int} +@test M == MMatrix{3,3,Int,9} m = zero(M) v = SymTracelessTensorValue(m) @test isa(v,SymTracelessTensorValue{3,Int}) M2 = Mutable(v) @test M == M2 +@test M[ v ] == M[ m ] + +M = Mutable(SkewSymTensorValue{3,Int}) +@test M == MMatrix{3,3,Int,9} +m = zero(M) +v = SkewSymTensorValue(m) +@test isa(v,SkewSymTensorValue{3,Int}) +M2 = Mutable(v) +@test M == M2 +@test M[ v ] == M[ m ] M = Mutable(ThirdOrderTensorValue{3,1,2,Int}) -@test M == MArray{Tuple{3,1,2},Int} +@test M == MArray{Tuple{3,1,2},Int,3,6} m = zero(M) v = ThirdOrderTensorValue(m) @test isa(v,ThirdOrderTensorValue{3,1,2,Int}) M2 = Mutable(v) @test M == M2 +@test M[ v ] == M[ m ] -@test_throws ErrorException Mutable(SymFourthOrderTensorValue{2,Int}) # @notimplemented +M = Mutable(SymFourthOrderTensorValue{2,Int}) +@test M == MArray{Tuple{2,2,2,2},Int,4,16} +m = zero(M) +v = SymFourthOrderTensorValue(m) +@test isa(v,SymFourthOrderTensorValue{2,Int}) +M2 = Mutable(v) +@test M == M2 +@test M[ v ] == M[ m ] @test_throws ErrorException Mutable(MultiValue) # @abstractmethod +# conversion with scalar for Tensors with exactly one physical component +v = VectorValue(1) +t2 = TensorValue(1) +s = SymTensorValue(1) +t3 = ThirdOrderTensorValue(1) +f = SymFourthOrderTensorValue(1) +@test Float64[ v ] == [ 1. ] +@test Float64[ t2 ] == [ 1. ] +@test Float64[ s ] == [ 1. ] +@test Float64[ t3 ] == [ 1. ] +@test Float64[ f ] == [ 1. ] +@test VectorValue{1}[ 1 ] == [ v ] +@test TensorValue{1,1,Int,1}[ 1 ] == [ t2 ] +@test SymTensorValue{1,Int}[ 1 ] == [ s ] +@test ThirdOrderTensorValue{1,1,1,Int,1}[ 1 ] == [ t3 ] +@test SymFourthOrderTensorValue{1,Int}[ 1 ] == [ f ] + +v = VectorValue(1,2) +st = SymTracelessTensorValue{1}() +sk = SkewSymTensorValue(1) +@test Float64[ st ] == [ 0. ] +@test_throws ErrorException Float64[ v ] +@test_throws ErrorException Float64[ sk ] +@test_throws ErrorException VectorValue{2,Int,2}[ 1 ] +@test_throws ErrorException SymTracelessTensorValue{1,Int,0}[ 1 ] +@test_throws ErrorException SkewSymTensorValue{1,Int,1}[ 1 ] + +# component number APIs @test num_components(Int) == 1 @test num_components(Float64) == 1 @test num_components(1.0) == 1 @test num_components(1) == 1 @test num_components(VectorValue{3,Float64}) == 3 +@test num_components(AbstractSymTensorValue{2}) == 4 @test num_components(VectorValue(1,2,3)) == 3 @test num_components(TensorValue(1,2,3,4)) == 4 @test num_components(SymTensorValue(1,2,3)) == 4 @@ -681,7 +841,7 @@ M2 = Mutable(v) @test_throws ErrorException num_components(VectorValue) @test_throws ErrorException num_components(TensorValue) @test_throws ErrorException num_components(TensorValue{2}) -@test_throws ErrorException num_components(AbstractSymTensorValue{2}) +@test_throws ErrorException num_components(AbstractSymTensorValue) @test_throws ErrorException num_components(SymTensorValue) @test_throws ErrorException num_components(SymTracelessTensorValue) @test_throws ErrorException num_components(ThirdOrderTensorValue{2,2}) @@ -736,13 +896,13 @@ a = SymTensorValue(11,21,22) @test change_eltype(SymTensorValue{2,Float64},Int) == SymTensorValue{2,Int} @test isa(Tuple(a),Tuple) @test Tuple(a) == a.data -b = Matrix{Int64}(undef,2,2) +b = Matrix{Int}(undef,2,2) b[1,1] = a[1,1] b[1,2] = a[1,2] b[2,1] = a[2,1] b[2,2] = a[2,2] a = SymTensorValue(11,21,22) -bt = SymTensorValue{2,Int64}(b) +bt = SymTensorValue{2,Int}(b) @test all(bt .== a) a = SymTracelessTensorValue(11,21) @@ -750,12 +910,12 @@ a = SymTracelessTensorValue(11,21) @test change_eltype(SymTracelessTensorValue{2,Float64},Int) == SymTracelessTensorValue{2,Int} @test isa(Tuple(a),Tuple) @test Tuple(a) == a.data -b = Matrix{Int64}(undef,2,2) +b = Matrix{Int}(undef,2,2) b[1,1] = a[1,1] b[1,2] = a[1,2] b[2,1] = a[2,1] b[2,2] = a[2,2] -bt = SymTracelessTensorValue{2,Int64}(b) +bt = SymTracelessTensorValue{2,Int}(b) @test all(bt .== a) a = SymFourthOrderTensorValue(1111,1121,1122, 2111,2121,2122, 2211,2221,2222) @@ -764,4 +924,21 @@ a = SymFourthOrderTensorValue(1111,1121,1122, 2111,2121,2122, 2211,2221,2222) @test isa(Tuple(a),Tuple) @test Tuple(a) == a.data +for V in (VectorValue, SymTensorValue, SkewSymTensorValue, + SymTracelessTensorValue, SymFourthOrderTensorValue) + + VD = V{3,Float32} + u = rand(VD) + comp_basis = component_basis(VD) + dual_basis_rpzs = representatives_of_componentbasis_dual(VD) + @test comp_basis == component_basis(u) + @test dual_basis_rpzs == representatives_of_componentbasis_dual(u) + + @test length(comp_basis) == length(dual_basis_rpzs) == num_indep_components(VD) + @test eltype(comp_basis) <: VD + @test eltype(dual_basis_rpzs) <: VD + @test u ≈ sum( indep_comp_getindex(u,i) * Vi for (i,Vi) in enumerate(comp_basis) ) + @test u ≈ V( (u ⊙ Vi for Vi in dual_basis_rpzs)... ) +end + end # module TypesTests diff --git a/test/moment_based_reffes.jl b/test/moment_based_reffes.jl new file mode 100644 index 000000000..ca4ee610e --- /dev/null +++ b/test/moment_based_reffes.jl @@ -0,0 +1,312 @@ + +using Gridap +using Gridap.ReferenceFEs +using Gridap.Fields +using Gridap.Arrays +using Gridap.Geometry + +using FillArrays + +using Gridap.ReferenceFEs: MonomialBasis, JacobiBasis +using Gridap.Polynomials, Gridap.TensorValues + +############################################################################################ +# Fixes + +function Arrays.return_cache(k::Fields.BroadcastOpFieldArray{typeof(∘)},x::AbstractArray{<:Point}) + f, g = k.args + cg = return_cache(g,x) + gx = evaluate!(cg,g,x) + cf = return_cache(f,gx) + return cg, cf +end + +function Arrays.evaluate!(cache, k::Fields.BroadcastOpFieldArray{typeof(∘)}, x::AbstractArray{<:Point}) + cg, cf = cache + f, g = k.args + gx = evaluate!(cg,g,x) + fgx = evaluate!(cf,f,gx) + return fgx +end + +function Arrays.return_value(k::Fields.OperationField{typeof(∘)},x::AbstractArray{<:Point}) + f, g = k.fields + gx = return_value(g,x) + fgx = return_value(f,gx) + return fgx +end + +function Arrays.return_cache(k::Fields.OperationField{typeof(∘)},x::AbstractArray{<:Point}) + f, g = k.fields + cg = return_cache(g,x) + gx = evaluate!(cg,g,x) + cf = return_cache(f,gx) + return cg, cf +end + +function Arrays.evaluate!(cache, k::Fields.OperationField{typeof(∘)}, x::AbstractArray{<:Point}) + cg, cf = cache + f, g = k.fields + gx = evaluate!(cg,g,x) + fgx = evaluate!(cf,f,gx) + return fgx +end + +Arrays.return_type(::Polynomials.QGradMonomialBasis{D,T}) where {D,T} = T + +############################################################################################ + +# Doesnt work... +# function Arrays.return_value(k::Broadcasting{<:typeof(∘)},args::Union{Field,AbstractArray{<:Field}}...) +# f, g = args +# Fields.BroadcastOpFieldArray(f,g) +# end +# +# function Arrays.evaluate!(cache,k::Broadcasting{<:typeof(∘)},args::Union{Field,AbstractArray{<:Field}}...) +# f, g = args +# Fields.BroadcastOpFieldArray(f,g) +# end + +############################################################################################ +# FaceMeasure + +struct FaceMeasure{Df,Dc} + poly::Polytope{Dc} + face::Int + quad::Quadrature + function FaceMeasure{Df}(poly::Polytope{Dc},face::Int,order::Int) where {Df,Dc} + fpoly = get_face_polytope(poly,Df,face) + quad = Quadrature(fpoly,order) + new{Df,Dc}(poly,face,quad) + end +end + +function get_face_polytope(p::Polytope{Dc},Df::Int,face::Int) where Dc + return get_reffaces(p)[get_face_type(p)[get_offset(p,Df) + face]] +end + +function get_normal(m::FaceMeasure{Df,Dc}) where {Df,Dc} + @assert Df == Dc - 1 + n = get_facet_normal(m.poly) + return ConstantField(n[m.face]) +end + +function get_tangent(m::FaceMeasure{1,Dc}) where {Dc} + t = get_edge_tangent(m.poly) + return ConstantField(t[m.face]) +end + +function Geometry.get_cell_map(m::FaceMeasure{Df,Dc}) where {Df,Dc} + if Df == Dc + return GenericField(identity) + end + fp = get_face_polytope(m.poly,Df,m.face) + coords = get_face_coordinates(m.poly,Df)[m.face] + fields = get_shapefuns(LagrangianRefFE(Float64,fp,1)) + return linear_combination(coords,fields) +end + +function get_extension(m::FaceMeasure{Df,Dc}) where {Df,Dc} + @assert Df == Dc - 1 + vs = ReferenceFEs._nfaces_vertices(Float64,m.poly,Df)[m.face] + return ConstantField(TensorValue(hcat([vs[2]-vs[1]...],[vs[3]-vs[1]...]))) +end + +function Arrays.evaluate(f,ds::FaceMeasure) + x = get_coordinates(ds.quad) + w = get_weights(ds.quad) + fx = evaluate(f,x) + return w .* fx, x +end + +############################################################################################ +# Constant basis: Basis for a tensor type +# Another possible name would be "component basis + +constant_basis(T::Type{<:Real}) = [one(T)] +function constant_basis(V::Type{<:MultiValue}) + T = eltype(V) + n = num_components(V) + z, o = zero(T), one(T) + return [V(ntuple(i -> ifelse(i == j, o, z),Val(n))) for j in 1:n] +end + +############################################################################################ +using Gridap.ReferenceFEs: ReferenceFEName, Conformity +struct MomentBasedReffe{T<:ReferenceFEName} <: ReferenceFEName + name :: T +end + +""" +A moment is given by a triplet (f,σ,μ) where + - f is id of a face Fk + - σ is a function σ(φ,μ,ds) that returns a Field-like object to be integrated over Fk + - μ is a polynomials basis on Fk + +Open questions: + - Do we want to keep having structures face -> data? I guess if we had more than a single + moment per face, we would aggregate them. + - Can we always determine the minimum integration order for each moment? + +Current pains: +- ReferenceFEs is loaded before CellData, i.e we do NOT have access to the + CellField machinery to compute the moments. +- Most operations that are defined for CellFields are not 100% working for arrays of Fields, + where we tend to use the Broadcasting + Operation machinery. + For example, ∇(φ) is explicitly deactivated in favor of Broadcasting(∇)(φ). +""" +function MomentBasedReferenceFE( + name::ReferenceFEName, + p::Polytope{D}, + prebasis::AbstractVector{<:Field}, + moments::AbstractVector{<:Tuple{<:Integer,<:Function,<:AbstractArray{<:Field}}}, + conformity::Conformity; +) where D + + # TODO: Basis of the constants for the tensor-type we have + T = return_type(prebasis) + order = get_order(prebasis) + φ = constant_basis(VectorValue{D,T}) + + # TODO: This has to be something that can fully contract with the prebasis φ + V = VectorValue{D,Float64} + + # TODO: Do we want these of length n_moments or n_faces? + n_faces = num_faces(p) + n_moments = length(moments) + face_moments = Vector{Array{V}}(undef, n_moments) + face_nodes = Vector{UnitRange{Int}}(undef, n_moments) + face_own_dofs = [Int[] for _ in 1:n_faces] + nodes = Point{D}[] + + k = 1 + n_nodes = 1 + n_dofs = 1 + for (face,σ,μ) in moments + d = get_facedims(p)[face] + lface = face - get_offset(p,d) + + qdegree = order + get_order(μ) + 1 + ds = FaceMeasure{d}(p, lface, qdegree) + + fmap = get_cell_map(ds) + φf = transpose(Broadcasting(Operation(∘))(map(constant_field,φ),fmap)) + vals, f_coords = evaluate(σ(φf,μ,ds),ds) + coords = evaluate(fmap,f_coords) + + face_moments[k] = map(v -> v⋅φ, eachslice(vals, dims=(1,2))) # (nN, nμ, nφ) ⋅ nφ -> (nN, nμ) + face_nodes[k] = n_nodes:(n_nodes+length(coords)-1) + append!(nodes, coords) + append!(face_own_dofs[face], n_dofs:(n_dofs+size(vals,2)-1)) + + k += 1 + n_nodes += length(coords) + n_dofs += size(vals,2) + end + + dof_basis = MomentBasedDofBasis(nodes, face_moments, face_nodes) + metadata = nothing + + return GenericRefFE{typeof(MomentBasedReffe(name))}( + n_dofs, p, prebasis, dof_basis, conformity, metadata, face_own_dofs + ) +end + +function cmom(φ,μ,ds) + Broadcasting(Operation(⋅))(φ,μ) +end + +function fmom_dot(φ,μ,ds) + n = get_normal(ds) + φn = Broadcasting(Operation(⋅))(φ,n) + Broadcasting(Operation(*))(φn,μ) +end + +function fmom_cross(φ,μ,ds) + o = get_facet_orientations(ds.poly)[ds.face] # Why do we need this? Is this to avoid a sign map? + n = o*get_normal(ds) + E = get_extension(ds) + Eμ = Broadcasting(Operation(⋅))(E,μ) # We have to extend the basis to 3D (see Nedelec) + φn = Broadcasting(Operation(×))(n,φ) + Broadcasting(Operation(⋅))(φn,Eμ) +end + +function emom(φ,μ,ds) + t = get_tangent(ds) + φt = Broadcasting(Operation(⋅))(φ,t) + Broadcasting(Operation(*))(φt,μ) +end + +# RT implementation + +D = 2 +p = (D==2) ? QUAD : HEX +order = 1 + +prebasis = QCurlGradMonomialBasis(Val(D), Float64,order) +cb = QGradJacobiPolynomialBasis(Val(D), Float64,order-1) +fb = JacobiBasis(Float64,SEGMENT,order) +moments = [ + [(f+get_offset(p,1),fmom_dot,fb) for f in 1:num_faces(p,1)]..., # Face moments + (num_faces(p),cmom,cb) # Cell moments +] +reffe = MomentBasedReferenceFE(RaviartThomas(),p,prebasis,moments,DivConformity()) +dofs = get_dof_basis(reffe) + +rt_reffe = RaviartThomasRefFE(Float64,p,order) +rt_dofs = get_dof_basis(rt_reffe) + +Mrt = evaluate(rt_dofs,prebasis) +M = evaluate(dofs,prebasis) +M == Mrt + +# ND implementation + +D = 2 +p = (D==2) ? QUAD : HEX +order = 1 + +prebasis = QGradMonomialBasis(Val(D),Float64,order) +cb = QCurlGradMonomialBasis(Val(D),Float64,order-1) +fb = QGradMonomialBasis(Val(D-1),Float64,order-1) +eb = MonomialBasis(Float64,SEGMENT,order) +moments = [ + [(f+get_offset(p,1),emom,eb) for f in 1:num_faces(p,1)]..., # Edge moments + (num_faces(p),cmom,cb) # Cell moments +] +reffe = MomentBasedReferenceFE(Nedelec{1}(),p,prebasis,moments,CurlConformity()) +dofs = get_dof_basis(reffe) + +nd_reffe = NedelecRefFE(Float64,p,order) +nd_dofs = get_dof_basis(nd_reffe) + +Mnd = evaluate(nd_dofs,prebasis) +M = evaluate(dofs,prebasis) +M == Mnd + +# 3D ND implementation + +D = 3 +p = (D==2) ? QUAD : HEX +order = 1 + +prebasis = QGradMonomialBasis(Val(D),Float64,order) +cb = QCurlGradMonomialBasis(Val(D),Float64,order-1) +fb = QGradMonomialBasis(Val(D-1),Float64,order-1) +eb = MonomialBasis(Float64,SEGMENT,order) +moments = [ + [(f+get_offset(p,1),emom,eb) for f in 1:num_faces(p,1)]..., # Edge moments + [(f+get_offset(p,2),fmom_cross,fb) for f in 1:num_faces(p,2)]..., # Face moments + (num_faces(p),cmom,cb) # Cell moments +] +reffe = MomentBasedReferenceFE(Nedelec{1}(),p,prebasis,moments,CurlConformity()) +dofs = get_dof_basis(reffe) + +nd_reffe = NedelecRefFE(Float64,p,order) +nd_dofs = get_dof_basis(nd_reffe) + +Mnd = evaluate(nd_dofs,prebasis) +M = evaluate(dofs,prebasis) +M == Mnd + +############################################################################################ diff --git a/test/pullbacks.jl b/test/pullbacks.jl new file mode 100644 index 000000000..716c68658 --- /dev/null +++ b/test/pullbacks.jl @@ -0,0 +1,53 @@ + +using FillArrays +using Gridap +using Gridap.ReferenceFEs, Gridap.FESpaces, Gridap.CellData +using Gridap.Fields + +using Gridap.ReferenceFEs: Pullback + +model = CartesianDiscreteModel((0,2,0,4),(2,2)) +Ω = Triangulation(model) +dΩ = Measure(Ω,2) +pts = CellData.get_data(get_cell_points(dΩ)) + +reffe = RaviartThomasRefFE(Float64,QUAD,1) +V = FESpace(model,reffe) + +u(x) = VectorValue(x[1], -x[2]) +uh = interpolate(u,V) + +φ_phys = get_fe_basis(V).cell_basis +φ_ref = φ_phys.args[1] +Jt, sign = φ_phys.args[2:end] + +σ_phys = get_fe_dof_basis(V).cell_dof +σ_ref = Fill(get_dof_basis(reffe),num_cells(model)) + +App = lazy_map(evaluate,σ_phys,φ_phys)[1] +Arr = lazy_map(evaluate,σ_ref,φ_ref)[1] + +pf = ContraVariantPiolaMap() + +# Inverse Pushforward +ipf = inverse_map(pf) +f_ref = lazy_map(ipf,φ_phys,Jt,sign) +Brr = lazy_map(evaluate,σ_ref,f_ref)[1] +Brr == Arr +φ_ref_x = lazy_map(evaluate,φ_ref,pts)[1] +f_ref_x = lazy_map(evaluate,f_ref,pts)[1] +f_ref_x ≈ φ_ref_x + +# Pullback +pb = Pullback(pf) +θ_ref = lazy_map(pb,σ_phys,Jt,sign) +θ_ref_x = lazy_map(evaluate,θ_ref,φ_ref) +σ_ref_x = lazy_map(evaluate,σ_ref,φ_ref) +θ_ref_x ≈ σ_ref_x + +# Inverse Pullback +ipb = inverse_map(pb) +θ_phys = lazy_map(ipb,σ_ref,Jt,sign) +θ_phys_x = lazy_map(evaluate,θ_phys,φ_phys) +σ_phys_x = lazy_map(evaluate,σ_phys,φ_phys) +θ_phys_x ≈ σ_phys_x