diff --git a/NEWS.md b/NEWS.md
index 9ac89f20e..be4ea7e64 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [Unreleased]
+
+- Documentation and refactoring of Gridap.Polynomials. Since PR[#TODO](https://github.com/gridap/Gridap.jl/pull/#TODO).
+- 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 `UniformPolyBasis` and `CompWiseTensorPolyBasis` respectively.
+- `PCurlGradMonomialBasis` has been generalized to `Legendre` and `Chebyshev` using the new `RaviartThomasPolyBasis`.
+- New aliases and high level constructor for `UniformPolyBasis` (former MonomialBasis): `MonomialBasis`, `LegendreBasis`, `ChebyshevBasis` and `BernsteinBasis`.
+- New high level constructors for Nedelec and Raviart-Thomas polynomial bases:
+ - Nedelec on simplex `PGradBasis(PT<:Polynomial, Val(D), order)`
+ - Nedelec on n-cubes `QGradBasis(PT<:Polynomial, Val(D), order)`
+ - Raviart on simplex `PCurlGradBasis(PT<:Polynomial, Val(D), order)`
+ - Raviart on n-cubes `QCurlGradBasis(PT<:Polynomial, Val(D), order)`
+
## [0.18.10] - 2025-03-04
### Added
@@ -35,6 +48,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Low level optimisations to reduce allocations. `AffineMap` renamed to `AffineField`. New `AffineMap <: Map`, doing the same as `AffineField` without struct allocation. New `ConstantMap <: Map`, doing the same as `ConstantField` without struct allocation. Since PR[#1043](https://github.com/gridap/Gridap.jl/pull/1043).
- `ConstantFESpaces` can now be built on triangulations. Since PR[#1069](https://github.com/gridap/Gridap.jl/pull/1069)
+- 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 `UniformPolyBasis{...,Monomial}`
+- All polynomial bases are now subtypes of the new abstract type `PolynomialBasis <: AbstractVector{<:Polynomial}`
+- `get_order(b::(Q/P)[Curl]Grad...)`, now returns the order of the basis, +1 than the order parameter passed to the constructor.
+- `NedelecPreBasisOnSimplex` is renamed `NedelecPolyBasisOnSimplex`
+- `JacobiPolynomial` is renamed `Legendre` and subtypes `Polynomial`
+- `JacobiPolynomialBasis` is renamed `LegendreBasis`
+- `ModalC0BasisFunction` is renamed `ModalC0` and subtypes `Polynomial`
+
+### 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 `[...]GradBasis(Monomial, Val(D), args...)`
+- `NedelecPreBasisOnSimplex{D}(args...)` in favor of `NedelecPolyBasisOnSimplex(Val(D), args...)`
+- `JacobiPolynomialBasis{D}(args...)` in favor of `LegendreBasis(Val(D), args...)`
+
## [0.18.8] - 2024-12-2
### Added
diff --git a/docs/Project.toml b/docs/Project.toml
index 44ea2eafb..96ff808c5 100644
--- a/docs/Project.toml
+++ b/docs/Project.toml
@@ -1,6 +1,7 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Gridap = "56d4f2e9-7ea1-5844-9cf6-b9c51ca7ce8e"
+Kroki = "b3565e16-c1f2-4fe9-b4ab-221c88942068"
[compat]
Documenter = "1.0"
diff --git a/docs/generate_diagrams.jl b/docs/generate_diagrams.jl
new file mode 100644
index 000000000..ba0e7fc3f
--- /dev/null
+++ b/docs/generate_diagrams.jl
@@ -0,0 +1,85 @@
+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 UniformPolyBasis {
+ +get_exponents
+ +get_orders
+ }
+ struct CompWiseTensorPolyBasis
+ struct RaviartThomasPolyBasis
+ struct NedelecPolyBasisOnSimplex
+ struct ModalC0Basis {
+ +get_orders
+ }
+ }
+
+ PolynomialBasis <|-- UniformPolyBasis
+ PolynomialBasis <|-- CompWiseTensorPolyBasis
+ PolynomialBasis <|-- RaviartThomasPolyBasis
+ PolynomialBasis <|-- NedelecPolyBasisOnSimplex
+ PolynomialBasis <|-- ModalC0Basis
+
+ object "(<:Polynomial)Basis" as m1
+ object "QGrad[<:Polynomial]Basis\nQCurlGrad[<:Polynomial]Basis" as m2
+ object "PCurlGrad[<:Polynomial]Basis" as m4
+ object "PGradMonomialBasis" as m5
+ UniformPolyBasis <-down- m1
+ CompWiseTensorPolyBasis <-down- m2
+ RaviartThomasPolyBasis <-down- m4
+ NedelecPolyBasisOnSimplex <-down- m5
+
+ @enduml
+ """
+
diff --git a/docs/make.jl b/docs/make.jl
index eda6f02dd..eaa82166a 100644
--- a/docs/make.jl
+++ b/docs/make.jl
@@ -23,6 +23,7 @@ pages = [
"Gridap.Adaptivity" => "Adaptivity.md",
"Developper notes" => Any[
"dev-notes/block-assemblers.md",
+ "dev-notes/pullbacks.md",
"dev-notes/autodiff.md",
],
]
@@ -30,7 +31,8 @@ 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,
diff --git a/docs/src/Adaptivity.md b/docs/src/Adaptivity.md
index 5a698528a..04e0b3d62 100644
--- a/docs/src/Adaptivity.md
+++ b/docs/src/Adaptivity.md
@@ -156,6 +156,4 @@ When a cell is refined, we need to be able to evaluate the fields defined on the
```@docs
FineToCoarseField
-FineToCoarseDofBasis
-FineToCoarseRefFE
```
diff --git a/docs/src/ODEs.md b/docs/src/ODEs.md
index 3701457b9..abd79dd57 100644
--- a/docs/src/ODEs.md
+++ b/docs/src/ODEs.md
@@ -44,10 +44,12 @@ More precisely, we would like ``\boldsymbol{u}_{n}`` to be close to ``\boldsymbo
```
This is a condition on the design of the pair (``\mathcal{I}``, ``\mathcal{F}``).
-# Classification of ODEs and numerical schemes
+## Classification of ODEs and numerical schemes
+
Essentially, a numerical scheme converts a (continuous) ODE into (discrete) nonlinear systems of equations. These systems of equations can be linear under special conditions on the nature of the ODE and the numerical scheme. Since numerical methods for linear and nonlinear systems of equations can be quite different in terms of cost and implementation, we are interested in solving linear systems whenever possible. This leads us to perform the following classifications.
-## Classification of ODEs
+### Classification of ODEs
+
We define a few nonlinearity types based on the expression of the residual.
* **Nonlinear**. Nothing special can be said about the residual.
* **Quasilinear**. The residual is linear with respect to the highest-order time derivative and the corresponding linear form may depend on time and lower-order time derivatives, i.e.
@@ -83,7 +85,8 @@ In particular, for the global residual to be linear, both the implicit and expli
> 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/Polynomials.md b/docs/src/Polynomials.md
index 33a3e658c..c1f116dea 100644
--- a/docs/src/Polynomials.md
+++ b/docs/src/Polynomials.md
@@ -1,10 +1,303 @@
+# Gridap.Polynomials
```@meta
CurrentModule = Gridap.Polynomials
```
-# 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 ``\boldsymbol{K}=(K_1, K_2, \dots, K_D)`` D-dimensional monomial is defined by
+```math
+\boldsymbol{x} = (x_1, x_2, \dots, x_D) \longrightarrow
+\boldsymbol{x}^{\boldsymbol{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}\sum _{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)=\sum _{i=0}^{\left\lfloor {n}/{2}\right\rfloor }{\binom
+ {n}{2i}}\left(x^{2}-1\right)^{i}x^{n-2i},\qquad
+U_{n}(x)=\sum _{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 [`Bernstein`](@ref) polynomials forming a basis of ``\mathbb{P}_K`` are
+defined by
+```math
+B^K_{n}(x) = \binom{K}{n} x^n (1-x)^{K-n}\qquad\text{ for } 0\leq n\leq K.
+```
+They are positive on ``[0,1]`` and sum to ``1``.
+
+#### 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
+[`UniformPolyBasis`](@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 ``\mathbb{P}_K(x)`` the space of univariate polynomials of order up to ``K`` in the varible ``x``
+```math
+\mathbb{P}_K(x) = \text{Span}\big\{\quad x\rightarrow x^i \quad\big|\quad 0\leq i\leq K \quad\big\}.
+```
+
+Then, ``\mathbb{Q}^D`` and ``\mathbb{P}^D`` are the spaces for Lagrange elements
+on D-cubes and D-simplices respectively, defined by
+```math
+\mathbb{Q}^D_K = \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^\alpha \quad\big|\quad 0\leq
+ \alpha_1, \alpha_2, \dots, \alpha_D \leq K \quad\big\},
+```
+and
+```math
+\mathbb{P}^D_K = \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^\alpha \quad\big|\quad 0\leq
+ \alpha_1, \alpha_2, \dots, \alpha_D \leq K;\quad \sum_{d=1}^D \alpha_d \leq
+ K \quad\big\}.
+```
+
+To note, there is ``\mathbb{P}_K = \mathbb{P}^1_K = \mathbb{Q}^1_K``.
+
+#### Serendipity space Sr
+
+The serendipity space, commonly used for serendipity finite elements on n-cubes,
+are defined by
+```math
+\mathbb{S}r^D_K = \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^\alpha \quad\big|\quad 0\leq
+ \alpha_1, \alpha_2, \dots, \alpha_D \leq K;\quad
+ \sum_{d=1}^D \alpha_d\;\mathbb{1}_{[2,K]}(\alpha_d) \leq K \quad\big\}
+```
+where ``\mathbb{1}_{[2,K]}(\alpha_d)`` is ``1`` if ``\alpha_d\geq 2`` or else
+``0``.
+
+#### Homogeneous P and Q spaces
+
+It will later be useful to define the homogeneous Q spaces
+```math
+\tilde{\mathbb{Q}}^D_K = \mathbb{Q}^D_K\backslash\mathbb{Q}^D_{K-1} =
+ \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^\alpha \quad\big|\quad 0\leq \alpha_1,
+ \alpha_2, \dots, \alpha_D \leq K; \quad \text{max}(\alpha) = K \quad\big\},
+```
+and homogeneous P spaces
+```math
+\tilde{\mathbb{P}}^D_K = \mathbb{P}^D_K\backslash \mathbb{P}^D_{K-1} =
+ \text{Span}\big\{\quad \bm{x}\rightarrow\bm{x}^\alpha \quad\big|\quad 0\leq \alpha_1,
+ \alpha_2, \dots, \alpha_D \leq K;\quad \sum_{d=1}^D \alpha_d = K \quad\big\}.
+```
+
+
+#### Nédélec spaces
+
+The Kᵗʰ Nédélec polynomial spaces on respectively rectangles and
+triangles are defined by
+```math
+\mathbb{ND}^2_K(\square) = \left(\mathbb{Q}^2_K\right)^2 \oplus
+ \left(\begin{array}{c} y^{K+1}\,\mathbb{P}_K(x)\\ x^{K+1}\,\mathbb{P}_K(y) \end{array}\right)
+,\qquad
+\mathbb{ND}^2_K(\bigtriangleup) =\left(\mathbb{P}^2_K\right)^2 \oplus\bm{x}\times(\tilde{\mathbb{P}}^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 ``\oplus`` is the direct sum of vector spaces.
+
+Then, the Kᵗʰ Nédélec polynomial spaces on respectively hexahedra and
+tetrahedra are defined by
+```math
+\mathbb{ND}^3_K(\square) = \left(\mathbb{Q}^3_K\right)^3 \oplus \bm{x}\times(\tilde{\mathbb{Q}}^3_K)^3,\qquad
+\mathbb{ND}^3_K(\bigtriangleup) =\left(\mathbb{P}^3_K\right)^3 \oplus \bm{x}\times(\tilde{\mathbb{P}}^3_K)^3.
+```
+
+``\mathbb{ND}^D_K(\square)`` and ``\mathbb{ND}^D_K(\bigtriangleup)`` are of
+order K+1 and the curl of their elements are in ``(\mathbb{Q}^D_K)^D``
+and ``(\mathbb{P}^D_K)^D`` respectively.
+
+#### Raviart-Thomas spaces
+
+The Kᵗʰ Raviart-Thomas polynomial spaces on respectively D-cubes and
+D-simplices are defined by
+```math
+\mathbb{ND}^D_K(\square) = \left(\mathbb{Q}^D_K\right)^D \oplus \bm{x}\;\tilde{\mathbb{Q}}^D_K, \qquad
+\mathbb{ND}^D_K(\bigtriangleup) = \left(\mathbb{P}^D_K\right)^D \oplus \bm{x}\;\tilde{\mathbb{P}}^D_K,
+```
+these bases are of dimension K+1 and the divergence of their elements are in
+``\mathbb{Q}^D_K`` and ``\mathbb{P}^D_K`` respectively.
+
+
+#### 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 ``\alpha`` previously
+used in the P/Q spaces definitions.
+
+When using [`hierarchical`](@ref isHierarchical) 1D bases, the following
+filters can be used to define associated polynomial spaces:
+
+| space | filter |
+| :-----------| :--------------------------------------------------------------- |
+| ℚᴰ | `_q_filter(e,order) = maximum(e) <= order` |
+| ℚᴰₙ\\ℚᴰₙ₋₁ | `_qs_filter(e,order) = maximum(e) == order` |
+| ℙᴰ | `_p_filter(e,order) = sum(e) <= order` |
+| ℙᴰₙ\\ℙᴰₙ₋₁ | `_ps_filter(e,order) = sum(e) == order` |
+| 𝕊rᴰₙ | `_ser_filter(e,order) = sum( [ i for i in e if i>1 ] ) <= order` |
+
+
+## Types for polynomial families
+
+The following types represent particular polynomial bases 'families' or 'types', later
+shortened as `PT` in type parameters.
+
+```@docs
+Polynomial
+```
+
+!!! 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
+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. ``\mathbb{P}``). Indeed, the
+ term numbers do not correspond to the degree of the polynomial, because the
+ basis is not [`hierarchical`](@ref isHierarchical).
+
+```@docs
+BernsteinBasis
+PGradBasis
+QGradBasis
+PCurlGradBasis
+QCurlGradBasis
+```
+## Low level APIs and internals
+
+
+
+```@docs
+UniformPolyBasis
+UniformPolyBasis(::Type, ::Val{D}, ::Type, ::Int, ::Function) where D
+UniformPolyBasis(::Type, ::Val{D}, ::Type{V}, ::NTuple{D,Int}, ::Function) where {D,V}
+get_orders
+get_exponents
+CompWiseTensorPolyBasis
+NedelecPolyBasisOnSimplex
+RaviartThomasPolyBasis
+ModalC0Basis
+ModalC0Basis()
+```
+
+### Deprecated APIs
-```@autodocs
-Modules = [Polynomials,]
+```@docs
+num_terms
+PGradMonomialBasis
+PCurlGradMonomialBasis
+QGradMonomialBasis
+QCurlGradMonomialBasis
+JacobiPolynomialBasis
```
diff --git a/docs/src/ReferenceFEs.md b/docs/src/ReferenceFEs.md
index 430b3cb4f..8cc845992 100644
--- a/docs/src/ReferenceFEs.md
+++ b/docs/src/ReferenceFEs.md
@@ -4,7 +4,87 @@ CurrentModule = Gridap.ReferenceFEs
# Gridap.ReferenceFEs
+```@contents
+Pages = ["ReferenceFEs.md"]
+Depth = 2:3
+```
+
+## Polytopes
+
+### Abstract API
+
+```@autodocs
+Modules = [ReferenceFEs,]
+Order = [:type, :constant, :macro, :function]
+Pages = ["/Polytopes.jl"]
+```
+
+### Extrusion Polytopes
+
+```@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","StrangeQuadratures.jl","XiaoGimbutasQuadratures.jl"]
+```
+
+## ReferenceFEs
+
+### Abstract API
+
```@autodocs
Modules = [ReferenceFEs,]
+Order = [:type, :constant, :macro, :function]
+Pages = ["ReferenceFEInterfaces.jl","Dofs.jl","LinearCombinationDofVectors.jl"]
```
+### Nodal ReferenceFEs
+
+```@autodocs
+Modules = [ReferenceFEs,]
+Order = [:type, :constant, :macro, :function]
+Pages = ["LagrangianRefFEs.jl","LagrangianDofBases.jl","SerendipityRefFEs.jl","BezierRefFEs.jl","ModalC0RefFEs.jl"]
+```
+
+### Moment-Based ReferenceFEs
+
+#### Framework
+
+```@autodocs
+Modules = [ReferenceFEs,]
+Order = [:type, :constant, :macro, :function]
+Pages = ["MomentBasedReferenceFEs.jl","Pullbacks.jl"]
+```
+
+#### Available Moment-Based ReferenceFEs
+
+```@autodocs
+Modules = [ReferenceFEs,]
+Order = [:type, :constant, :macro, :function]
+Pages = ["RaviartThomasRefFEs.jl","NedelecRefFEs.jl","BDMRefFEs.jl","CRRefFEs.jl"]
+```
diff --git a/docs/src/TensorValues.md b/docs/src/TensorValues.md
index 6b04631cf..5b32b22ba 100644
--- a/docs/src/TensorValues.md
+++ b/docs/src/TensorValues.md
@@ -34,7 +34,7 @@ C = inner.(g,B) # inner product of g against all TensorValues in the array B
# C = [2494 2494 2494 2494 2494]
```
-To create a [`::MultiValue`](@ref) tensor from components, these should be given
+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):
diff --git a/docs/src/assets/poly_1.svg b/docs/src/assets/poly_1.svg
new file mode 100644
index 000000000..2e3ffcf55
--- /dev/null
+++ b/docs/src/assets/poly_1.svg
@@ -0,0 +1 @@
+
\ 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..0299b85dc
--- /dev/null
+++ b/docs/src/assets/poly_2.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/src/dev-notes/pullbacks.md b/docs/src/dev-notes/pullbacks.md
new file mode 100644
index 000000000..2a2a98d13
--- /dev/null
+++ b/docs/src/dev-notes/pullbacks.md
@@ -0,0 +1,82 @@
+
+# FE basis transformations
+
+## Notation
+
+Consider a reference polytope ``\hat{K}``, mapped to the physical space by a **geometrical map** ``F``, i.e. ``K = F(\hat{K})``. Consider also a linear function space on the reference polytope ``\hat{V}``, and a set of unisolvent degrees of freedom represented by moments in the dual space ``\hat{V}^*``.
+
+Throughout this document, we will use the following notation:
+
+- ``\varphi \in V`` is a **physical field** ``\varphi : K \rightarrow \mathbb{R}^k``. A basis of ``V`` is denoted by ``\Phi = \{\varphi\}``.
+- ``\hat{\varphi} \in \hat{V}`` is a **reference field** ``\hat{\varphi} : \hat{K} \rightarrow \mathbb{R}^k``. A basis of ``\hat{V}`` is denoted by ``\hat{\Phi} = \{\hat{\varphi}\}``.
+- ``\sigma \in V^*`` is a **physical moment** ``\sigma : V \rightarrow \mathbb{R}``. A basis of ``V^*`` is denoted by ``\Sigma = \{\sigma\}``.
+- ``\hat{\sigma} \in \hat{V}^*`` is a **reference moment** ``\hat{\sigma} : \hat{V} \rightarrow \mathbb{R}``. A basis of ``\hat{V}^*`` is denoted by ``\hat{\Sigma} = \{\hat{\sigma}\}``.
+
+## Pullbacks and Pushforwards
+
+We define a **pushforward** map as ``F^* : \hat{V} \rightarrow V``, mapping reference fields to physical fields. Given a pushforward ``F^*``, we define:
+
+- The **pullback** ``F_* : V^* \rightarrow \hat{V}^*``, mapping physical moments to reference moments. Its action on physical dofs is defined in terms of the pushforward map ``F^*`` as ``\hat{\sigma} = F_*(\sigma) := \sigma \circ F^*``.
+- The **inverse pushforward** ``(F^*)^{-1} : V \rightarrow \hat{V}``, mapping physical fields to reference fields.
+- The **inverse pullback** ``(F_*)^{-1} : \hat{V}^* \rightarrow V^*``, mapping reference moments to physical moments. Its action on reference dofs is defined in terms of the inverse pushforward map ``(F^*)^{-1}`` as ``\sigma = (F_*)^{-1}(\hat{\sigma}) := \hat{\sigma} \circ (F^*)^{-1}``.
+
+## Change of basis
+
+In many occasions, we will have that (as a basis)
+
+```math
+\hat{\Sigma} \neq F_*(\Sigma), \quad \text{and} \quad \Phi \neq F^*(\hat{\Phi})
+```
+
+To maintain conformity and proper scaling in these cases, we define cell-dependent invertible changes of basis ``P`` and ``M``, such that
+
+```math
+\hat{\Sigma} = P F_*(\Sigma), \quad \text{and} \quad \Phi = M F^*(\hat{\Phi})
+```
+
+An important result from [1, Theorem 3.1] is that ``P = M^T``.
+
+!!! details
+ [1, Lemma 2.6]: A key ingredient is that given ``M`` a matrix we have ``\Sigma (M \Phi) = \Sigma (\Phi) M^T`` since
+ ```math
+ [\Sigma (M \Phi)]_{ij} = \sigma_i (M_{jk} \varphi_k) = M_{jk} \sigma_i (\varphi_k) = [\Sigma (\Phi) M^T]_{ij}
+ ```
+ where we have used that moments are linear.
+
+We then have the following diagram:
+
+```math
+\hat{V}^* \xleftarrow{P} \hat{V}^* \xleftarrow{F_*} V^* \\
+\hat{V} \xrightarrow{F^*} V \xrightarrow{P^T} V
+```
+
+!!! details
+ The above diagram is well defined, since we have
+ ```math
+ \hat{\Sigma}(\hat{\Phi}) = P F_* (\Sigma)(F^{-*} (P^{-T} \Phi)) = P \Sigma (F^* (F^{-*} P^{-T} \Phi)) = P \Sigma (P^{-T} \Phi) = P \Sigma (\Phi) P^{-1} = Id \\
+ \Sigma(\Phi) = F_*^{-1}(P^{-1}\hat{\Sigma})(P^T F^*(\hat{\Phi})) = P^{-1} \hat{\Sigma} (F^{-*}(P^T F^*(\hat{\Phi}))) = P^{-1} \hat{\Sigma} (P^T \hat{\Phi}) = P^{-1} \hat{\Sigma}(\hat{\Phi}) P = Id
+ ```
+
+From an implementation point of view, it is more natural to build ``P^{-1}`` and then retrieve all other matrices by transposition/inversion.
+
+## Interpolation
+
+In each cell ``K`` and for ``C_b^k(K)`` the space of functions defined on ``K`` with at least ``k`` bounded derivatives, we define the interpolation operator ``I_K : C_b^k(K) \rightarrow V`` as
+
+```math
+I_K(g) = \Sigma(g) \Phi \quad, \quad \Sigma(g) = P^{-1} \hat{\Sigma}(F^{-*}(g))
+```
+
+## Implementation notes
+
+!!! note
+ In [2], Covariant and Contravariant Piola maps preserve exactly (without any sign change) the normal and tangential components of a vector field.
+ I am quite sure that the discrepancy is coming from the fact that the geometrical information in the reference polytope is globally oriented.
+ For instance, the normals ``n`` and ``\hat{n}`` both have the same orientation, i.e ``n = (||\hat{e}||/||e||) (det J) J^{-T} \hat{n}``. Therefore ``\hat{n}`` is not fully local. See [2, Equation 2.11].
+ In our case, we will be including the sign change in the transformation matrices, which will include all cell-and-dof-dependent information.
+
+## References
+
+[1] [Kirby 2017, A general approach to transforming finite elements.](https://arxiv.org/abs/1706.09017)
+
+[2] [Aznaran et al. 2021, Transformations for Piola-mapped elements.](https://arxiv.org/abs/2110.13224)
diff --git a/src/Adaptivity/Adaptivity.jl b/src/Adaptivity/Adaptivity.jl
index 32a5d1594..140f4ab7b 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
@@ -45,7 +47,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..293746de8 100644
--- a/src/Adaptivity/MacroFEs.jl
+++ b/src/Adaptivity/MacroFEs.jl
@@ -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)
+ for fcell in 1:num_subcells(a.rrule)
+ 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
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/Arrays/Arrays.jl b/src/Arrays/Arrays.jl
index ce7452453..9a790df6a 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
@@ -47,9 +47,8 @@ export testargs
export inverse_map
export Broadcasting
-
export Operation
-
+export InverseMap
# LazyArray
diff --git a/src/Arrays/Maps.jl b/src/Arrays/Maps.jl
index 7552b80c8..77b5729cf 100644
--- a/src/Arrays/Maps.jl
+++ b/src/Arrays/Maps.jl
@@ -296,3 +296,16 @@ function inverse_map(f)
Function inverse_map is not implemented yet for objects of type $(typeof(f))
"""
end
+
+struct InverseMap{F} <: Map
+ original::F
+end
+
+function evaluate!(cache,k::InverseMap,args...)
+ @notimplemented """\n
+ The inverse evaluation is not implemented yet for maps of type $(typeof(k.original))
+ """
+end
+
+inverse_map(k::Map) = InverseMap(k)
+inverse_map(k::InverseMap) = k.original
diff --git a/src/CellData/CellData.jl b/src/CellData/CellData.jl
index 19efed72e..f94a41d7a 100644
--- a/src/CellData/CellData.jl
+++ b/src/CellData/CellData.jl
@@ -1,7 +1,6 @@
"""
-The exported names are
-$(EXPORTS)
+$(public_names_in_md(@__MODULE__))
"""
module CellData
@@ -87,7 +86,7 @@ export update_state!
export DiracDelta
-export SkeletonCellFieldPair
+export SkeletonCellFieldPair
include("CellDataInterface.jl")
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/FESpaces.jl b/src/FESpaces/FESpaces.jl
index b42054859..6302932ce 100644
--- a/src/FESpaces/FESpaces.jl
+++ b/src/FESpaces/FESpaces.jl
@@ -1,7 +1,6 @@
"""
-The exported names are
-$(EXPORTS)
+$(public_names_in_md(@__MODULE__))
"""
module FESpaces
@@ -219,9 +218,7 @@ include("UnconstrainedFESpaces.jl")
include("ConformingFESpaces.jl")
-include("DivConformingFESpaces.jl")
-
-include("CurlConformingFESpaces.jl")
+include("Pullbacks.jl")
include("FESpaceFactories.jl")
@@ -253,8 +250,6 @@ include("CLagrangianFESpaces.jl")
include("DirichletFESpaces.jl")
-#include("ExtendedFESpaces.jl")
-
include("FESpacesWithLinearConstraints.jl")
include("DiscreteModelWithFEMaps.jl")
diff --git a/src/FESpaces/Pullbacks.jl b/src/FESpaces/Pullbacks.jl
new file mode 100644
index 000000000..26386fc1d
--- /dev/null
+++ b/src/FESpaces/Pullbacks.jl
@@ -0,0 +1,129 @@
+
+# TODO: We probably want to export these from Gridap.ReferenceFEs
+using Gridap.ReferenceFEs: PushforwardRefFE, Pushforward, Pullback
+using Gridap.ReferenceFEs: ContraVariantPiolaMap, CoVariantPiolaMap
+
+function get_cell_dof_basis(
+ model::DiscreteModel, cell_reffe::AbstractArray{<:GenericRefFE{T}}, conformity::Conformity
+) where T <: PushforwardRefFE
+ pushforward, cell_change, cell_args = get_cell_pushforward(
+ Pushforward(T), model, cell_reffe, conformity
+ )
+ cell_ref_dofs = lazy_map(get_dof_basis, cell_reffe)
+ cell_phy_dofs = lazy_map(inverse_map(Pullback(pushforward)), cell_ref_dofs, cell_args...)
+ return lazy_map(linear_combination, cell_change, cell_phy_dofs) # TODO: Inverse and transpose
+end
+
+function get_cell_shapefuns(
+ model::DiscreteModel, cell_reffe::AbstractArray{<:GenericRefFE{T}}, conformity::Conformity
+) where T <: PushforwardRefFE
+ pushforward, cell_change, cell_args = get_cell_pushforward(
+ Pushforward(T), model, cell_reffe, conformity
+ )
+ cell_ref_fields = lazy_map(get_shapefuns, cell_reffe)
+ cell_phy_fields = lazy_map(pushforward, cell_ref_fields, cell_args...)
+ return lazy_map(linear_combination, cell_change, cell_phy_fields)
+end
+
+function get_cell_pushforward(
+ ::Pushforward, model::DiscreteModel, cell_reffe, conformity
+)
+ @abstractmethod
+end
+
+# ContraVariantPiolaMap
+
+function get_cell_pushforward(
+ ::ContraVariantPiolaMap, model::DiscreteModel, cell_reffe, conformity
+)
+ cell_map = get_cell_map(get_grid(model))
+ Jt = lazy_map(Broadcasting(∇),cell_map)
+ change = get_sign_flip(model, cell_reffe)
+ return ContraVariantPiolaMap(), change, (Jt,)
+end
+
+# CoVariantPiolaMap
+
+function get_cell_pushforward(
+ ::CoVariantPiolaMap, model::DiscreteModel, cell_reffe, conformity
+)
+ 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 CoVariantPiolaMap(), change, (Jt,)
+end
+
+# NormalSignMap
+
+"""
+ struct NormalSignMap <: Map
+ ...
+ end
+"""
+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
+ fill!(dof_sign, one(eltype(dof_sign)))
+
+ 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] = -1.0
+ 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/CurlConformingFESpaces.jl b/src/FESpaces/deprecated/CurlConformingFESpaces.jl
similarity index 92%
rename from src/FESpaces/CurlConformingFESpaces.jl
rename to src/FESpaces/deprecated/CurlConformingFESpaces.jl
index a16c2cc5e..55b3d484a 100644
--- a/src/FESpaces/CurlConformingFESpaces.jl
+++ b/src/FESpaces/deprecated/CurlConformingFESpaces.jl
@@ -2,8 +2,8 @@
function get_cell_dof_basis(
model::DiscreteModel,
cell_reffe::AbstractArray{<:GenericRefFE{Nedelec}},
- ::CurlConformity)
-
+ ::CurlConformity
+)
cell_dofs = lazy_map(get_dof_basis,cell_reffe)
cell_ownids = lazy_map(get_face_own_dofs,cell_reffe)
cell_map = get_cell_map(Triangulation(model))
@@ -45,7 +45,7 @@ function get_cell_shapefuns(
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 e99221107..33bbe385f 100644
--- a/src/Fields/AffineMaps.jl
+++ b/src/Fields/AffineMaps.jl
@@ -29,6 +29,12 @@ end
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
@@ -129,9 +135,3 @@ function lazy_map(
origins = a.args[2]
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
diff --git a/src/Fields/FieldArrays.jl b/src/Fields/FieldArrays.jl
index 12379b639..485de481b 100644
--- a/src/Fields/FieldArrays.jl
+++ b/src/Fields/FieldArrays.jl
@@ -368,6 +368,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)
@@ -392,6 +402,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))
@@ -509,6 +531,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
@@ -694,3 +732,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 0dd060b4c..bb01fc091 100644
--- a/src/Fields/Fields.jl
+++ b/src/Fields/Fields.jl
@@ -1,3 +1,7 @@
+"""
+
+$(public_names_in_md(@__MODULE__))
+"""
module Fields
using Gridap.Arrays
@@ -6,6 +10,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
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/Geometry/Geometry.jl b/src/Geometry/Geometry.jl
index d7d34ae53..33a219a36 100644
--- a/src/Geometry/Geometry.jl
+++ b/src/Geometry/Geometry.jl
@@ -1,7 +1,6 @@
"""
-Exported names are
-$(EXPORTS)
+$(public_names_in_md(@__MODULE__))
"""
module Geometry
diff --git a/src/Helpers/HelperFunctions.jl b/src/Helpers/HelperFunctions.jl
index 5fb5cda3d..b6b0cf20c 100644
--- a/src/Helpers/HelperFunctions.jl
+++ b/src/Helpers/HelperFunctions.jl
@@ -38,3 +38,29 @@ function first_and_tail(a::Tuple)
first(a), Base.tail(a)
end
+"""
+ public_names_in_md(m::Module)
+
+Return a string displaying exported and other public names of the module for
+printing in markdown.
+"""
+function public_names_in_md(m::Module)
+ 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
+ [`$(join(exported,"`](@ref), [`"))`](@ref)
+ """
+
+ isempty(non_exported_publics) && return s
+
+ s * """
+
+ ### Other public names
+ [`$(join(non_exported_publics,"`](@ref), [`"))`](@ref)
+ """
+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/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 318309aa1..3c666fb5d 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/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/Polynomials/BernsteinBases.jl b/src/Polynomials/BernsteinBases.jl
new file mode 100644
index 000000000..655f2b988
--- /dev/null
+++ b/src/Polynomials/BernsteinBases.jl
@@ -0,0 +1,228 @@
+"""
+ Bernstein <: Polynomial
+
+Type representing Bernstein polynomials, c.f. [Bernstein polynomials](@ref) section.
+"""
+struct Bernstein <: Polynomial end
+
+isHierarchical(::Type{Bernstein}) = false
+
+"""
+ BernsteinBasis{D,V,K} = UniformPolyBasis{D,V,K,Bernstein}
+
+Alias for Bernstein multivariate scalar' or `Multivalue`'d basis.
+"""
+const BernsteinBasis{D,V,K} = UniformPolyBasis{D,V,K,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...) = UniformPolyBasis(Bernstein, args...)
+
+
+# 1D evaluation implementation
+
+"""
+ binoms(::Val{K})
+
+Returns the tuple of binomials ( C₍ₖ₀₎, C₍ₖ₁₎, ..., C₍ₖₖ₎ ).
+"""
+binoms(::Val{K}) where K = ntuple( i -> binomial(K,i-1), Val(K+1))
+
+
+function _evaluate_1d!(::Type{Bernstein},::Val{0},v::AbstractMatrix{T},x,d) where {T<:Number}
+ @inbounds v[d,1] = one(T)
+end
+
+@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]
+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},::Val{K},v::AbstractMatrix{T},x,d) where {K,T<:Number}
+ @inbounds begin
+ 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)
+ ## 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]
+ end
+ end
+ # still optimisable for K > 2/3:
+ # - compute bj = binoms(k,j) at compile time (binoms(Val(K)) function)
+ # - 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
+
+function _gradient_1d!(::Type{Bernstein},::Val{0},g::AbstractMatrix{T},x,d) where {T<:Number}
+ @inbounds g[d,1] = zero(T)
+end
+function _gradient_1d!(::Type{Bernstein},::Val{1},g::AbstractMatrix{T},x,d) where {T<:Number}
+ o = one(T)
+ @inbounds g[d,1] = -o
+ @inbounds g[d,2] = o
+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},::Val{K}, g::AbstractMatrix{T},x,d) where {K,T<:Number}
+ @inbounds begin
+ n = K + 1 # n > 2
+
+ # De Casteljau for Bᵏ⁻¹ⱼ for j = k-1, k-2, ..., 1
+ _evaluate_1d!(Bernstein,Val(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
+
+
+function _hessian_1d!(::Type{Bernstein},::Val{0},h::AbstractMatrix{T},x,d) where {T<:Number}
+ @inbounds h[d,1] = zero(T)
+end
+function _hessian_1d!(::Type{Bernstein},::Val{1},h::AbstractMatrix{T},x,d) where {T<:Number}
+ @inbounds h[d,1] = zero(T)
+ @inbounds h[d,2] = zero(T)
+end
+function _hessian_1d!(::Type{Bernstein},::Val{2},h::AbstractMatrix{T},x,d) where {T<:Number}
+ o = one(T)
+ @inbounds h[d,1] = 2o
+ @inbounds h[d,2] = -4o
+ @inbounds h[d,3] = 2o
+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},::Val{K},h::AbstractMatrix{T},x,d) where {K,T<:Number}
+ @inbounds begin
+ n = K + 1 # n > 3
+ KK = K*(K-1)
+
+ # De Casteljau for Bᵏ⁻²ⱼ for j = k-2, k-3, ..., 1
+ _evaluate_1d!(Bernstein,Val(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},v::Val_01,t::NTuple{2},x,d)
+ @inline _evaluate_1d!(Bernstein, v, t[1], x, d)
+ @inline _gradient_1d!(Bernstein, v, t[2], x, d)
+end
+
+function _derivatives_1d!(::Type{Bernstein},::Val{K},t::NTuple{2},x,d) where K
+ @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,Val(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},v::Val_012,t::NTuple{3},x,d)
+ @inline _evaluate_1d!(Bernstein, v, t[1], x, d)
+ @inline _gradient_1d!(Bernstein, v, t[2], x, d)
+ @inline _hessian_1d!( Bernstein, v, t[3], x, d)
+end
+
+function _derivatives_1d!(::Type{Bernstein},::Val{K},t::NTuple{3},x,d) where K
+ @inbounds begin
+ n = K + 1 # n > 3
+ v, g, h = t
+
+ KK = K*(K-1)
+ λ2 = x[d]
+ λ1 = one(eltype(v)) - λ2
+
+ # De Casteljau until Bᵏ⁻²ⱼ ∀j
+ _evaluate_1d!(Bernstein,Val(K-2),v,x,d)
+
+ # Compute hessians as _hessian_1d!
+ 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 _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
diff --git a/src/Polynomials/ChangeBasis.jl b/src/Polynomials/ChangeBasis.jl
index d10427338..8380b365c 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))
diff --git a/src/Polynomials/ChebyshevBases.jl b/src/Polynomials/ChebyshevBases.jl
new file mode 100644
index 000000000..913b58e21
--- /dev/null
+++ b/src/Polynomials/ChebyshevBases.jl
@@ -0,0 +1,85 @@
+"""
+ 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 end
+
+isHierarchical(::Type{<:Chebyshev}) = true
+
+"""
+ ChebyshevBasis{D,V,kind,K} = UniformPolyBasis{D,V,K,Chebyshev{kind}}
+
+Alias for Chebyshev multivariate scalar' or `Multivalue`'d basis.
+"""
+const ChebyshevBasis{D,V,kind,K} = UniformPolyBasis{D,V,K,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) = UniformPolyBasis(Chebyshev{kind}, args...)
+
+function UniformPolyBasis(
+ ::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}},::Val{0},c::AbstractMatrix{T},x,d) where {kind,T<:Number}
+
+ @inbounds c[d,1] = one(T)
+end
+
+function _evaluate_1d!(
+ ::Type{Chebyshev{kind}},::Val{K},c::AbstractMatrix{T},x,d) where {kind,K,T<:Number}
+
+ 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}},::Val{0},g::AbstractMatrix{T},x,d) where T<:Number
+
+ @inbounds g[d,1] = zero(T)
+end
+
+function _gradient_1d!(
+ ::Type{Chebyshev{:T}},::Val{K},g::AbstractMatrix{T},x,d) where {K,T<:Number}
+
+ 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
+
+_gradient_1d!(::Type{Chebyshev{:U}},::Val{K},h::AbstractMatrix{T},x,d) where {K,T<:Number} = @notimplemented
+_hessian_1d!( ::Type{Chebyshev{:U}},::Val{K},h::AbstractMatrix{T},x,d) where {K,T<:Number} = @notimplemented
+
diff --git a/src/Polynomials/CompWiseTensorPolyBases.jl b/src/Polynomials/CompWiseTensorPolyBases.jl
new file mode 100644
index 000000000..f0206e125
--- /dev/null
+++ b/src/Polynomials/CompWiseTensorPolyBases.jl
@@ -0,0 +1,330 @@
+"""
+ CompWiseTensorPolyBasis{D,V,K,PT,L} <: PolynomialBasis{D,V,K,PT}
+
+"Polynomial basis of component wise tensor product polynomial spaces"
+
+Polynomial basis for a `D`-multivariate `V`-valued polynomial space:
+
+`V`(𝕊¹, 𝕊², ..., 𝕊ᴸ)
+
+with `L`>1, where the scalar `D`-multivariate spaces 𝕊ˡ (for 1 ≤ l ≤ `L`) of each
+(independent) component of `V` is the tensor product of 1D ℙ spaces of order
+α(l,n) for 1 ≤ n ≤ `D`, that is:
+
+𝕊¹ = ℙα(1,1) ⊗ … ⊗ ℙα(1,`D`)\\
+⋮\\
+𝕊ˡ = ⊗ₙ ℙα(l,n)\\
+⋮\\
+𝕊ᴸ = ℙα(`L`,1) ⊗ … ⊗ ℙα(`L`,`D`)
+
+The `L`×`D` matrix of orders α is given in the constructor, and `K` is the
+maximum of α. Any 1D polynomial family `PT<:Polynomial` is usable.
+"""
+struct CompWiseTensorPolyBasis{D,V,K,PT,L} <: PolynomialBasis{D,V,K,PT}
+ orders::SMatrix{L,D,Int}
+
+ function CompWiseTensorPolyBasis{D}(
+ ::Type{PT}, ::Type{V}, orders::SMatrix{L,D,Int}) where {D,PT<:Polynomial,V,L}
+
+ msg1 = "The orders matrix rows number must match the number of independent components of V"
+ @check L == num_indep_components(V) msg1
+ msg2 = "The Component Wise construction is useless for one component, use UniformPolyBasis instead"
+ @check L > 1 msg2
+ @check D > 0
+ @check isconcretetype(PT) "PT needs to be a concrete <:Polynomial type"
+ K = maximum(orders)
+
+ new{D,V,K,PT,L}(orders)
+ end
+end
+
+Base.size(a::CompWiseTensorPolyBasis) = ( sum(prod.(eachrow(a.orders .+ 1))), )
+
+"""
+ 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)) )`.
+"""
+function get_comp_terms(f::CompWiseTensorPolyBasis{D,V,K,PT,L}) where {D,V,K,PT,L}
+ _terms(l) = CartesianIndices( Tuple(f.orders[l,:] .+ 1) )
+ comp_terms = ntuple(l -> _terms(l), Val(L))
+ comp_terms::NTuple{L,CartesianIndices{D}}
+end
+
+
+#################################
+# nD evaluations implementation #
+#################################
+
+function _evaluate_nd!(
+ b::CompWiseTensorPolyBasis{D,V,K,PT,L}, x,
+ r::AbstractMatrix{V}, i,
+ c::AbstractMatrix{T}) where {D,V,K,PT,L,T}
+
+ orders = b.orders
+ comp_terms = get_comp_terms(b)
+
+ for d in 1:D
+ # for each coordinate d, the order at which the basis should be evaluated is
+ # the maximum d-order for any component l
+ Kd = Val(maximum(orders[:,d]))
+ _evaluate_1d!(PT,Kd,c,x,d)
+ end
+
+ m = zero(Mutable(V))
+ k = 1
+
+ for (l,terms) in enumerate(comp_terms)
+ for ci in terms
+
+ s = one(T)
+ @inbounds 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,K,PT,L}, x,
+ r::AbstractMatrix{G}, i,
+ c::AbstractMatrix{T},
+ g::AbstractMatrix{T},
+ s::MVector{D,T}) where {D,V,K,PT,L,G,T}
+
+ orders = b.orders
+ comp_terms = get_comp_terms(b)
+
+ for d in 1:D
+ # for each spatial coordinate d, the order at which the basis should be
+ # evaluated is the maximum d-order for any component l
+ Kd = Val(maximum(orders[:,d]))
+ _derivatives_1d!(PT,Kd,(c,g),x,d)
+ end
+
+ k = 1
+
+ for (l,terms) in enumerate(comp_terms)
+ for ci in terms
+
+ for i in eachindex(s)
+ s[i] = one(T)
+ end
+
+ for q in 1:D
+ for d in 1:D
+ if d != q
+ @inbounds s[q] *= c[d,ci[d]]
+ else
+ @inbounds s[q] *= g[d,ci[d]]
+ end
+ end
+ end
+
+ k = _comp_wize_set_derivative!(r,i,s,k,Val(l),V)
+ end
+ end
+end
+
+"""
+ _comp_wize_set_derivative!(r::AbstractMatrix{G},i,s,k,::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`ᵗʰ.
+"""
+@generated function _comp_wize_set_derivative!(
+ r::AbstractMatrix{G},i,s,k,::Val{l},::Type{V}) where {G,l,V}
+
+ N_val_dims = length(size(V))
+ s_size = size(G)[1:end-N_val_dims]
+
+ body = "T = eltype(s); z = zero(T);"
+ m = Array{String}(undef, size(G))
+ m .= "z"
+
+ for ci in CartesianIndices(s_size)
+ m[ci,l] = "(@inbounds s[$ci])"
+ end
+ body *= "@inbounds r[i,k] = ($(join(tuple(m...), ", ")));"
+
+ body = Meta.parse(string("begin ",body," end"))
+ return Expr(:block, body ,:(return k+1))
+end
+
+# See _uniform_set_derivative!(r::AbstractMatrix{G},i,s,k,::Type{V}) where {G,V<:AbstractSymTensorValue{D}} where D
+@generated function _comp_wize_set_derivative!(
+ r::AbstractMatrix{G},i,s,k,::Type{V}) where {G,V<:AbstractSymTensorValue{D}} where D
+
+ @notimplemented
+end
+
+function _hessian_nd!(
+ b::CompWiseTensorPolyBasis{D,V,K,PT,L}, x,
+ r::AbstractMatrix{H}, i,
+ c::AbstractMatrix{T},
+ g::AbstractMatrix{T},
+ h::AbstractMatrix{T},
+ s::MMatrix{D,D,T}) where {D,V,K,PT,L,H,T}
+
+ orders = b.orders
+ comp_terms = get_comp_terms(b)
+
+ for d in 1:D
+ # for each spatial coordinate d, the order at which the basis should be
+ # evaluated is the maximum d-order for any component l
+ Kd = Val(maximum(orders[:,d]))
+ _derivatives_1d!(PT,Kd,(c,g,h),x,d)
+ end
+
+ k = 1
+
+ for (l,terms) in enumerate(comp_terms)
+ for ci in terms
+
+ for i in eachindex(s)
+ s[i] = one(T)
+ end
+
+ 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
+ @inbounds s[r,q] *= h[d,ci[d]]
+ else
+ @inbounds s[r,q] *= g[d,ci[d]]
+ end
+ end
+ end
+ end
+
+ k = _comp_wize_set_derivative!(r,i,s,k,Val(l),V)
+ end
+ end
+end
+
+
+################################
+# Basis for Nedelec on D-cubes #
+################################
+
+"""
+ QGradBasis(::Type{PT}, ::Val{D}, ::Type{T}, order::Int) :: PolynomialBasis
+
+Return a basis of
+
+ℕ𝔻ᴰₙ(□) = (ℚᴰₙ)ᴰ ⊕ x × (ℚᴰₙ \\ ℚᴰₙ₋₁)ᴰ
+
+with n=`order`, the polynomial space for Nedelec elements on `D`-dimensional
+cubes with scalar type `T`.
+
+The `order`=n argument has the following meaning: the curl of the functions in
+this basis is in (ℚᴰₙ)ᴰ.
+
+`PT<:Polynomial` is the choice of the family of the scalar 1D basis polynomials.
+
+# Example:
+
+```jldoctest
+# a basis for Nedelec on hexahedra with divergence in ℚ₂
+b = QGradBasis(Monomial, Val(3), Float64, 2)
+```
+
+For more details, see [`CompWiseTensorPolyBasis`](@ref), as `QGradBasis` returns
+an instance of\\
+`CompWiseTensorPolyBasis{D, VectorValue{D,T}, order+1, PT}` for `D`>1, or\\
+`UniformPolyBasis{1, VectorValue{1,T}, order+1, PT}` for `D`=1.
+"""
+function QGradBasis(::Type{PT},::Val{D},::Type{T},order::Int) where {PT,D,T}
+ @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value"
+
+ V = VectorValue{D,T}
+ m = [ order + (i==j ? 0 : 1) for i in 1:D, j in 1:D ]
+ orders = SMatrix{D,D,Int}(m)
+ CompWiseTensorPolyBasis{D}(PT, V, orders)
+end
+
+function QGradBasis(::Type{PT},::Val{1},::Type{T},order::Int) where {PT,T}
+ @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value"
+
+ V = VectorValue{1,T}
+ UniformPolyBasis(PT, Val(1), V, order+1)
+end
+
+
+#######################################
+# Basis for Raviart-Thomas on D-cubes #
+#######################################
+
+"""
+ QCurlGradBasis(::Type{PT}, ::Val{D}, ::Type{T}, order::Int) :: PolynomialBasis
+
+Return a basis of
+
+ℝ𝕋ᴰₙ(□) = (ℚᴰₙ)ᴰ ⊕ x (ℚᴰₙ \\ ℚᴰₙ₋₁)
+
+with n=`order`, the polynomial space for Raviart-Thomas elements on
+`D`-dimensional cubes with scalar type `T`.
+
+The `order`=n argument has the following meaning: the divergence of the functions
+in this basis is in ℚᴰₙ.
+
+`PT<:Polynomial` is the choice of the family of the scalar 1D basis polynomials.
+
+# Example:
+
+```jldoctest
+# a basis for Raviart-Thomas on rectangles with divergence in ℚ₃
+b = QCurlGradBasis(Bernstein, Val(2), Float64, 3)
+```
+
+For more details, see [`CompWiseTensorPolyBasis`](@ref), as `QCurlGradBasis`
+returns an instance of\\
+`CompWiseTensorPolyBasis{D, VectorValue{D,T}, order+1, PT}` for `D`>1, or\\
+`UniformPolyBasis{1, VectorValue{1,T}, order+1, PT}` for `D`=1.
+"""
+function QCurlGradBasis(::Type{PT},::Val{D},::Type{T},order::Int) where {PT,D,T}
+ @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value"
+
+ V = VectorValue{D,T}
+ m = [ order + (i==j ? 1 : 0) for i in 1:D, j in 1:D ]
+ orders = SMatrix{D,D,Int}(m)
+ CompWiseTensorPolyBasis{D}(PT, V, orders)
+end
+
+function QCurlGradBasis(::Type{PT},::Val{1},::Type{T},order::Int) where {PT,T}
+ @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value"
+
+ V = VectorValue{1,T}
+ UniformPolyBasis(PT, Val(1), V, order+1)
+end
diff --git a/src/Polynomials/Deprecated.jl b/src/Polynomials/Deprecated.jl
new file mode 100644
index 000000000..6d5b1ed2d
--- /dev/null
+++ b/src/Polynomials/Deprecated.jl
@@ -0,0 +1,89 @@
+"""
+ 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...)
+
+"""
+ PGradMonomialBasis{D}(args...) where D
+
+!!! warning
+ Deprecated in favor of PGradBasis(Monomial, Val(D), args...).
+"""
+struct PGradMonomialBasis{D}
+ function PGradMonomialBasis()
+ @unreachable
+ new{0}()
+ end
+end
+@deprecate PGradMonomialBasis{D}(args...) where D PGradBasis(Monomial, Val(D), args...) false
+
+"""
+ PCurlGradMonomialBasis{D}(args...) where D
+
+!!! warning
+ Deprecated in favor of PCurlGradBasis(Monomial, Val(D), args...).
+"""
+struct PCurlGradMonomialBasis{D}
+ function PCurlGradMonomialBasis()
+ @unreachable
+ new{0}()
+ end
+end
+@deprecate PCurlGradMonomialBasis{D}(args...) where D PCurlGradBasis(Monomial, Val(D), args...)
+
+"""
+ QGradMonomialBasis{D}(args...) where D
+
+!!! warning
+ Deprecated in favor of QGradBasis(Monomial, Val(D), args...).
+"""
+struct QGradMonomialBasis{D}
+ function QGradMonomialBasis()
+ @unreachable
+ new{0}()
+ end
+end
+@deprecate QGradMonomialBasis{D}(args...) where D QGradBasis(Monomial, Val(D), args...)
+
+"""
+ QCurlGradMonomialBasis{D}(args...) where D
+
+!!! warning
+ Deprecated in favor of QCurlGradBasis(Monomial, Val(D), args...).
+"""
+struct QCurlGradMonomialBasis{D}
+ function QCurlGradMonomialBasis()
+ @unreachable
+ new{0}()
+ end
+end
+@deprecate QCurlGradMonomialBasis{D}(args...) where D QCurlGradBasis(Monomial, Val(D), args...)
+
+struct NedelecPreBasisOnSimplex{D}
+ function NedelecPreBasisOnSimplex()
+ @unreachable
+ new{0}()
+ end
+end
+@deprecate NedelecPreBasisOnSimplex{D}(args...) where D NedelecPolyBasisOnSimplex{D}(args...) false
+
+"""
+ JacobiPolynomialBasis{D}(args...) where D
+
+!!! warning
+ Deprecated in favor of LegendreBasis(Val(D), args...).
+"""
+struct JacobiPolynomialBasis{D}
+ function JacobiPolynomialBasis()
+ @unreachable
+ new{0}()
+ end
+end
+@deprecate JacobiPolynomialBasis{D}(args...) where D LegendreBasis(Val(D), args...)
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..9b5109d47
--- /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,K} = UniformPolyBasis{D,V,K,Legendre}
+
+Alias for Legendre multivariate scalar' or `Multivalue`'d basis.
+"""
+const LegendreBasis{D,V,K} = UniformPolyBasis{D,V,K,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...) = UniformPolyBasis(Legendre, args...)
+
+
+# 1D evaluation implementation
+
+# TODO optimize evaluation by using the iterative formula explicitely
+
+function _evaluate_1d!(::Type{Legendre},::Val{K},c::AbstractMatrix{T},x,d) where {K,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},::Val{K},g::AbstractMatrix{T},x,d) where {K,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},::Val{K},h::AbstractMatrix{T},x,d) where {K,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..42cc40a0c 100644
--- a/src/Polynomials/ModalC0Bases.jl
+++ b/src/Polynomials/ModalC0Bases.jl
@@ -1,267 +1,131 @@
-struct ModalC0BasisFunction <: Field end
+"""
+ ModalC0 <: Polynomial
+
+Type representing ModalC0 polynomials, c.f. [ModalC0 polynomials](@ref) section.
-struct ModalC0Basis{D,T,V} <: AbstractVector{ModalC0BasisFunction}
+Reference: Eq. (17) in https://doi.org/10.1016/j.camwa.2022.09.027
+"""
+struct ModalC0 <: Polynomial end
+
+"""
+ ModalC0Basis{D,V,T,K} <: PolynomialBasis{D,V,K,ModalC0}
+
+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,K} <: PolynomialBasis{D,V,K,ModalC0}
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 uniform, 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(a::ModalC0Basis{D,V}) where {D,V} = (length(a.terms)*num_indep_components(V),)
-"""
- 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
-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 +160,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 +172,81 @@ 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
+function _evaluate_nd!(
+ basis::ModalC0Basis{D,V,T,K}, x,
+ r::AbstractMatrix{V}, i,
+ c::AbstractMatrix{T}) where {D,V,T,K}
-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}
+ 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,T,K}, x,
+ r::AbstractMatrix{G}, i,
c::AbstractMatrix{T},
g::AbstractMatrix{T},
- ::Type{V}) where {G,T,D,V}
+ s::MVector{D,T}) where {D,V,T,K,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 +255,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 +283,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 +294,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,T,K}, 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}) where {D,V,T,K,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 +342,95 @@ 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
+ @assert 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
+ @assert 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
+ @assert 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 UniformPolyBasis etc.
+# Make it for x∈[0,1] like the other 1D bases.
+
+function _evaluate_1d!(::Type{ModalC0},::Val{K},c::AbstractMatrix{T},x,d) where {K,T<:Number}
+ a = zero(x)
+ b = zero(x) .+ one(T)
+ @inline _evaluate_1d_mc0!(c,x,a,b,K,d)
+end
+
+function _gradient_1d!(::Type{ModalC0},::Val{K},g::AbstractMatrix{T},x,d) where {K,T<:Number}
+ a = zero(x)
+ b = zero(x) .+ one(T)
+ @inline _gradient_1d_mc0!(g,x,a,b,K,d)
+end
+function _hessian_1d!(::Type{ModalC0},::Val{K},h::AbstractMatrix{T},x,d) where {K,T<:Number}
+ 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 f1452f34b..f37a7ab55 100644
--- a/src/Polynomials/MonomialBases.jl
+++ b/src/Polynomials/MonomialBases.jl
@@ -1,601 +1,80 @@
-struct Monomial <: Field 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()
+isHierarchical(::Type{Monomial}) = true
"""
- MonomialBasis{D}(::Type{T}, orders::Tuple [, filter::Function]) where {D,T}
+ MonomialBasis{D,V,K} = UniformPolyBasis{D,V,K,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 monomial Multivariate scalar' or `Multivalue`'d basis.
"""
-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,K} = UniformPolyBasis{D,V,K,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}
-
- orders = tfill(order,Val{D}())
- MonomialBasis{D}(T,orders,filter)
-end
-
-# API
-
-"""
- get_exponents(b::MonomialBasis)
-
-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{<:Point},
- TisbitsType::Val{true}) where {D,T}
-
- f = fg.fa
- r, v, c, g = cache
- z = zero(Mutable(VectorValue{D,eltype(T)}))
- 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}
-
- 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
- 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}
-
- 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
- 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
+MonomialBasis(args...) = UniformPolyBasis(Monomial, args...)
-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
+function PGradBasis(::Type{Monomial},::Val{D},::Type{T},order::Int) where {D,T}
+ NedelecPolyBasisOnSimplex{D}(Monomial,T,order)
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
+function PGradBasis(::Type{Monomial},::Val{1},::Type{T},order::Int) where T
+ @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value"
+ V = VectorValue{1,T}
+ UniformPolyBasis(Monomial, Val(1), V, order+1)
end
-# Helpers
-
-_q_filter(e,o) = true
+# 1D evaluation implementation
-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) ]
-end
-
-function _evaluate_1d!(v::AbstractMatrix{T},x,order,d) where T
- n = order + 1
- z = one(T)
- @inbounds v[d,1] = z
+function _evaluate_1d!(::Type{Monomial},::Val{K},c::AbstractMatrix{T},x,d) where {K,T<:Number}
+ n = K + 1
+ xn = one(T)
@inbounds xd = x[d]
- xn = xd
- for i in 2:n
- @inbounds v[d,i] = xn
- xn *= xd
- end
-end
-function _gradient_1d!(v::AbstractMatrix{T},x,order,d) where T
- n = order + 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
+ for i in 1:n
+ @inbounds c[d,i] = xn
xn *= xd
end
end
-function _hessian_1d!(v::AbstractMatrix{T},x,order,d) where T
- n = order + 1
+
+function _gradient_1d!(::Type{Monomial},::Val{K},g::AbstractMatrix{T},x,d) where {K,T<:Number}
+ n = K + 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)
- for i in 3:n
- @inbounds v[d,i] = (i-1)*(i-2)*xn
+ @inbounds xd = x[d]
+
+ @inbounds g[d,1] = z
+ for i in 2:n
+ @inbounds g[d,i] = (i-1)*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
+function _hessian_1d!(::Type{Monomial},::Val{0},h::AbstractMatrix{T},x,d) where {T<:Number}
+ @inbounds h[d,1] = zero(T)
end
-function _set_value!(v::AbstractVector{V},s::T,k) where {V,T}
- ncomp = num_indep_components(V)
+function _hessian_1d!(::Type{Monomial},::Val{K},h::AbstractMatrix{T},x,d) where {K,T<:Number}
+ n = K + 1 # n>1
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)
-
- 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
-
- 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 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
- 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
-
- 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)
+ xn = one(T)
+ @inbounds xd = x[d]
+ @inbounds h[d,1] = z
+ @inbounds h[d,2] = z
+ for i in 3:n
+ @inbounds h[d,i] = (i-1)*(i-2)*xn
+ xn *= xd
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..a5d49af9d
--- /dev/null
+++ b/src/Polynomials/NedelecPolyBases.jl
@@ -0,0 +1,276 @@
+"""
+ NedelecPolyBasisOnSimplex{D,V,K,PT} <: PolynomialBasis{D,V,K,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`.
+
+Currently, the basis is implemented as the union of a UniformPolyBasis{...,PT}
+for ℙᴰₙ and a monomial basis for x × (ℙᴰₙ \\ ℙᴰₙ₋₁)ᴰ.
+"""
+struct NedelecPolyBasisOnSimplex{D,V,K,PT} <: PolynomialBasis{D,V,K,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"
+ @notimplementedif !(D in (2,3))
+ K = Int(order)+1
+ V = VectorValue{D,T}
+ new{D,V,K,PT}(Int(order))
+ end
+end
+
+function Base.size(a::NedelecPolyBasisOnSimplex{D}) where D
+ k = a.order+1
+ n = div(k*prod(i->(k+i),2:D),factorial(D-1))
+ (n,)
+end
+
+function return_cache(
+ f::NedelecPolyBasisOnSimplex{D,V,K,PT},x::AbstractVector{<:Point}) where {D,V,K,PT}
+
+ np = length(x)
+ ndofs = length(f)
+ a = zeros(V,(np,ndofs))
+ P = UniformPolyBasis(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,K,PT}},
+ x::AbstractVector{<:Point}) where {D,V,K,PT}
+ f = g.fa
+ np = length(x)
+ ndofs = length(f)
+ xi = testitem(x)
+ G = gradient_type(V,xi)
+ a = zeros(G,(np,ndofs))
+ mb = UniformPolyBasis(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
+
+####################################
+# Basis for Nedelec on D-simplices #
+####################################
+
+"""
+ PGradBasis(::Type{Monomial}, ::Val{D}, ::Type{T}, order::Int) :: PolynomialBasis
+
+Return a basis of
+
+ℕ𝔻ᴰₙ(△) = (ℙᴰₙ)ᴰ ⊕ x × (ℙᴰₙ \\ ℙᴰₙ₋₁)ᴰ
+
+with n=`order`, the polynomial space for Nedelec elements on `D`-dimensional
+simplices with scalar type `T`. `D` must be 1, 2 or 3.
+
+The `order`=n argument has the following meaning: the curl of the functions in
+this basis is in (ℙᴰₙ)ᴰ.
+
+# Example:
+
+```jldoctest
+# a basis for Nedelec on tetrahedra with curl in ℙ₂
+b = PGradBasis(Monomial, Val(3), Float64, 2)
+```
+"""
+function PGradBasis(::Type{PT},::Val{D},::Type{T},order::Int) where {PT,D,T}
+ # Although NedelecPolyBasisOnSimplex can be constructed with any PT1 ] ) <= 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}
+
+ @assert D == length(eltype(x)) "Incorrect number of point components"
+ 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)))
+ # 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, t...)
+end
+
+function return_cache(f::PolynomialBasis{D,V}, x::AbstractVector{<:Point}) where {D,V}
+ _return_cache(f,x,V,Val(0))
+end
+
+function return_cache(
+ fg::FieldGradientArray{N,<:PolynomialBasis{D,V}},
+ x::AbstractVector{<:Point}) where {N,D,V}
+
+ f = fg.fa
+ xi = testitem(x)
+ G = V
+ 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
+
+function evaluate!(cache,
+ f::PolynomialBasis,
+ x::AbstractVector{<:Point})
+
+ r, c = cache
+ np = length(x)
+ _setsize!(f,np,r,c)
+ for i in 1:np
+ @inbounds xi = x[i]
+ _evaluate_nd!(f,xi,r,i,c)
+ end
+ r.array
+end
+
+function evaluate!(cache,
+ fg::FieldGradientArray{1,<:PolynomialBasis{D,V}},
+ x::AbstractVector{<:Point}) where {D,V}
+
+ f = fg.fa
+ r, c, g = cache
+ np = length(x)
+ _setsize!(f,np,r,c,g)
+ s = zero(Mutable(VectorValue{D,eltype(V)}))
+ for i in 1:np
+ @inbounds xi = x[i]
+ _gradient_nd!(f,xi,r,i,c,g,s)
+ end
+ r.array
+end
+
+function evaluate!(cache,
+ fg::FieldGradientArray{2,<:PolynomialBasis{D,V}},
+ x::AbstractVector{<:Point}) where {D,V}
+
+ f = fg.fa
+ r, c, g, h = cache
+ np = length(x)
+ _setsize!(f,np,r,c,g,h)
+ s = zero(Mutable(TensorValue{D,D,eltype(V)}))
+ for i in 1:np
+ @inbounds xi = x[i]
+ _hessian_nd!(f,xi,r,i,c,g,h,s)
+ end
+ r.array
+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)
+
+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 setting the result in the `i`th
+row of `r`.
+"""
+function _evaluate_nd!(
+ b::PolynomialBasis, xi,
+ r::AbstractMatrix, i,
+ c::AbstractMatrix)
+
+ @abstractmethod
+end
+
+"""
+ _gradient_nd!(b,xi,r,i,c,g,s)
+
+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 a mutable `D`×`K` cache (for the 1D polynomials first derivatives).
+- `s` is a mutable length `D` cache for ∇`b`ₖ(`xi`).
+"""
+function _gradient_nd!(
+ b::PolynomialBasis, xi,
+ r::AbstractMatrix, i,
+ c::AbstractMatrix,
+ g::AbstractMatrix,
+ s::MVector)
+
+ @abstractmethod
+end
+
+"""
+ _hessian_nd!(b,xi,r,i,c,g,h,s)
+
+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 a mutable `D`×`K` cache (for the 1D polynomials second derivatives).
+- `s` is a mutable `D`×`D` cache for H`b`ₖ(`xi`).
+"""
+function _hessian_nd!(
+ b::PolynomialBasis, xi,
+ r::AbstractMatrix, i,
+ c::AbstractMatrix,
+ g::AbstractMatrix,
+ h::AbstractMatrix,
+ s::MMatrix)
+
+ @abstractmethod
+end
+
+
+###############################
+# 1D internal polynomial APIs #
+###############################
+
+"""
+ _evaluate_1d!(PT::Type{<:Polynomial},::Val{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},::Val{K},c::AbstractMatrix{T},x,d) where {K,T<:Number}
+ @abstractmethod
+end
+
+"""
+ _gradient_1d!(PT::Type{<:Polynomial},::Val{K},g,x,d)
+
+Like [`_evaluate_1d!`](@ref), but computes the first derivative of the basis
+polynomials.
+"""
+function _gradient_1d!(::Type{<:Polynomial},::Val{K},g::AbstractMatrix{T},x,d) where {K,T<:Number}
+ @abstractmethod
+end
+
+"""
+ _hessian_1d!(PT::Type{<:Polynomial},::Val{K},g,x,d)
+
+Like [`_evaluate_1d!`](@ref), but computes the second derivative of the basis
+polynomials.
+"""
+function _hessian_1d!(::Type{<:Polynomial},::Val{K},h::AbstractMatrix{T},x,d) where {K,T<:Number}
+ @abstractmethod
+end
+
+# Dispatch helpers for base cases
+const Val_01 = Union{Val{0},Val{1}}
+const Val_012 = Union{Val{0},Val{1},Val{2}}
+
+"""
+ _derivatives_1d!(PT::Type{<:Polynomial}, ::Val{K}, (c,g,...), x, d)
+
+Same as calling
+```
+_evaluate_1d!(PT, Val(K), c, x d)
+_gradient_1d!(PT, Val(K), g, x d)
+ ⋮
+```
+but with possible performance optimization.
+"""
+function _derivatives_1d!( ::Type{<:Polynomial},v::Val,t::NTuple{N},x,d) where N
+ @abstractmethod
+end
+
+function _derivatives_1d!(PT::Type{<:Polynomial},v::Val,t::NTuple{1},x,d)
+ @inline _evaluate_1d!(PT, v, t[1], x, d)
+end
+
+function _derivatives_1d!(PT::Type{<:Polynomial},v::Val,t::NTuple{2},x,d)
+ @inline _evaluate_1d!(PT, v, t[1], x, d)
+ @inline _gradient_1d!(PT, v, t[2], x, d)
+end
+
+function _derivatives_1d!(PT::Type{<:Polynomial},v::Val,t::NTuple{3},x,d)
+ @inline _evaluate_1d!(PT, v, t[1], x, d)
+ @inline _gradient_1d!(PT, v, t[2], x, d)
+ @inline _hessian_1d!( PT, v, t[3], x, d)
+end
diff --git a/src/Polynomials/Polynomials.jl b/src/Polynomials/Polynomials.jl
index d2c622288..b4fb645f2 100644
--- a/src/Polynomials/Polynomials.jl
+++ b/src/Polynomials/Polynomials.jl
@@ -1,9 +1,80 @@
"""
-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)`
+- ℚₙ\\ℚₙ₋₁: `[Polynomial]Basis(..., Polynomials._qs_filter)`
+- ℙₙ\\ℙₙ₋₁: `[Polynomial]Basis(..., Polynomials._ps_filter)`
+- ℕ𝔻(△): [`PGradBasis`](@ref)`(Val(D), T, order)`
+- ℕ𝔻(□): [`QGradBasis`](@ref)`(...)`
+- ℝ𝕋(△): [`PCurlGradBasis`](@ref)`(...)`
+- ℝ𝕋(□): [`QCurlGradBasis`](@ref)`(...)`
+
+### 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 ℙ₂
+b = PGradBasis(Monomial, Val(3), Float64, 2) # basis of order 3
+
+# a basis for Nedelec on hexahedra with divergence in ℚ₂
+b = QGradBasis(Bernstein, Val(3), Float64, 2) # basis of order 3
+
+# a basis for Raviart-Thomas on tetrahedra with divergence in ℙ₂
+b = PCurlGradBasis(Chebyshev{:T}, Val(3), Float64, 2) # basis of order 3
+
+# a basis for Raviart-Thomas on rectangles with divergence in ℚ₃
+b = QCurlGradBasis(Bernstein, Val(2), Float64, 3) # basis of order 4
+```
+
+$(public_names_in_md(@__MODULE__))
"""
module Polynomials
@@ -21,28 +92,64 @@ import Gridap.Fields: evaluate!
import Gridap.Fields: return_cache
import Gridap.Arrays: return_type
+export Polynomial
+export isHierarchical
+export Monomial
+export Legendre
+export Chebyshev
+export ModalC0
+export Bernstein
+
+export PolynomialBasis
+export get_order
+
+export UniformPolyBasis
+export get_exponents
+export get_orders
export MonomialBasis
+export LegendreBasis
+export ChebyshevBasis
+export BernsteinBasis
+
+export CompWiseTensorPolyBasis
+export QGradBasis
+export QCurlGradBasis
+
+export NedelecPolyBasisOnSimplex
+export PGradBasis
+
+export RaviartThomasPolyBasis
+export PCurlGradBasis
+
+export ModalC0Basis
+
+# deprecated
+export num_terms
export QGradMonomialBasis
export QCurlGradMonomialBasis
export PCurlGradMonomialBasis
-export ModalC0Basis
-export JacobiPolynomialBasis
-export get_exponents
-export get_order
-export get_orders
-export num_terms
+
+include("PolynomialInterfaces.jl")
+
+include("UniformPolyBases.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("Deprecated.jl")
end # module
diff --git a/src/Polynomials/QCurlGradMonomialBases.jl b/src/Polynomials/QCurlGradMonomialBases.jl
deleted file mode 100644
index b0ad934dc..000000000
--- a/src/Polynomials/QCurlGradMonomialBases.jl
+++ /dev/null
@@ -1,75 +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()
-
-"""
- 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} = 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 2e69283ab..000000000
--- a/src/Polynomials/QGradMonomialBases.jl
+++ /dev/null
@@ -1,483 +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()
-
-"""
- 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()
-
-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..bc810f6ca
--- /dev/null
+++ b/src/Polynomials/RaviartThomasPolyBases.jl
@@ -0,0 +1,229 @@
+"""
+ RaviartThomasPolyBasis{D,V,K,PT} <: PolynomialBasis{D,V,K,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 is n+1 = `K`. `get_order` on it returns `K`.
+
+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 [`UniformPolyBasis`](@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,K,PT} <: PolynomialBasis{D,V,K,PT}
+ pterms::Vector{CartesianIndex{D}}
+ sterms::Vector{CartesianIndex{D}}
+
+ """
+ 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,order+1,PT}(pterms,sterms)
+ end
+end
+
+Base.size(a::RaviartThomasPolyBasis{D}) where {D} = (D*length(a.pterms) + length(a.sterms), )
+
+
+#################################
+# nD evaluations implementation #
+#################################
+
+function _evaluate_nd!(
+ b::RaviartThomasPolyBasis{D,V,K,PT}, x,
+ r::AbstractMatrix{V}, i,
+ c::AbstractMatrix{T}) where {D,V,K,PT,T}
+
+ pterms = b.pterms
+ sterms = b.sterms
+
+ for d in 1:D
+ Kv = Val(K)
+ _evaluate_1d!(PT,Kv,c,x,d)
+ end
+
+ m = zero(Mutable(V))
+ k = 1
+
+ @inbounds begin
+ for l in 1:D
+ for ci in 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 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,K,PT}, x,
+ r::AbstractMatrix{G}, i,
+ c::AbstractMatrix{T},
+ g::AbstractMatrix{T},
+ s::MVector{D,T}) where {D,V,K,PT,G,T}
+
+ pterms = b.pterms
+ sterms = b.sterms
+
+ for d in 1:D
+ Kv = Val(K)
+ _derivatives_1d!(PT,Kv,(c,g),x,d)
+ end
+
+ m = zero(Mutable(G))
+ k = 1
+
+ @inbounds begin
+ for l in 1:D
+ for ci in 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,Val(l),V)
+ end
+ end
+
+ for ci in 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
+
+"""
+ PCurlGradBasis(::Type{PT}, ::Val{D}, ::Type{T}, order::Int) :: PolynomialBasis
+
+Return a basis of
+
+ℝ𝕋ᴰₙ(△) = (ℙᴰₙ)ᴰ ⊕ x (ℙᴰₙ \\ ℙᴰₙ₋₁)
+
+with n=`order`, the polynomial space for Raviart-Thomas elements on
+`D`-dimensional simplices with scalar type `T`.
+
+The `order`=n argument of this function has the following meaning: the divergence
+of the functions in this basis is in ℙᴰₙ.
+
+`PT<:Polynomial` is the choice of the family of the scalar 1D basis polynomials,
+it must be hierarchical, see [`isHierarchical`](@ref).
+
+# Example:
+
+```jldoctest
+# a basis for Raviart-Thomas on tetrahedra with divergence in ℙ₂
+b = PCurlGradBasis(Monomial, Val(3), Float64, 2)
+```
+
+For more details, see [`RaviartThomasPolyBasis`](@ref), as `PCurlGradBasis` returns
+an instance of\\
+`RaviartThomasPolyBasis{D, VectorValue{D,T}, order+1, PT}` for `D`>1, or\\
+`UniformPolyBasis{1, VectorValue{1,T}, order+1, PT}` for `D`=1.
+"""
+function PCurlGradBasis(::Type{PT},::Val{D},::Type{T},order::Int) where {PT,D,T}
+ RaviartThomasPolyBasis{D}(PT, T, order)
+end
+
+function PCurlGradBasis(::Type{PT},::Val{1},::Type{T},order::Int) where {PT,T}
+ @check T<:Real "T needs to be <:Real since represents the type of the components of the vector value"
+
+ V = VectorValue{1,T}
+ UniformPolyBasis(PT, Val(1), V, order+1)
+end
diff --git a/src/Polynomials/UniformPolyBases.jl b/src/Polynomials/UniformPolyBases.jl
new file mode 100644
index 000000000..8068a9c48
--- /dev/null
+++ b/src/Polynomials/UniformPolyBases.jl
@@ -0,0 +1,376 @@
+#################################
+# Tensorial nD polynomial bases #
+#################################
+
+"""
+ struct UniformPolyBasis{D,V,K,PT} <: PolynomialBasis{D,V,K,PT}
+
+Type representing a uniform basis of (an)isotropic `D`-multivariate `V`-valued
+polynomial space
+
+`V`(𝕊, 𝕊, ..., 𝕊)
+
+where 𝕊 is a scalar multivariate polynomial space. So each (independant)
+component of `V` holds the same space (hence the name 'uniform').
+
+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.
+
+The fields of this `struct` are not public.
+This type fully implements the [`Field`](@ref) interface, with up to second
+order derivatives.
+"""
+struct UniformPolyBasis{D,V,K,PT} <: PolynomialBasis{D,V,K,PT}
+ orders::NTuple{D,Int}
+ terms::Vector{CartesianIndex{D}}
+
+ function UniformPolyBasis{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,K,PT}(orders,terms)
+ end
+end
+
+@inline Base.size(a::UniformPolyBasis{D,V}) where {D,V} = (length(a.terms)*num_indep_components(V),)
+
+function UniformPolyBasis(
+ ::Type{PT},
+ ::Val{D},
+ ::Type{V},
+ orders::NTuple{D,Int},
+ terms::Vector{CartesianIndex{D}}) where {PT<:Polynomial,D,V}
+
+ UniformPolyBasis{D}(PT,V,orders,terms)
+end
+
+"""
+ UniformPolyBasis(::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 UniformPolyBasis(
+ ::Type{PT}, ::Val{D}, ::Type{V}, orders::NTuple{D,Int}, filter::Function=_q_filter
+ ) where {PT,D,V}
+
+ terms = _define_terms(filter, orders)
+ UniformPolyBasis{D}(PT,V,orders,terms)
+end
+
+"""
+ UniformPolyBasis(::Type{PT}, ::Type{V}, ::Val{D}, order::Int [, filter=_q_filter])
+
+Return a `UniformPolyBasis{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 UniformPolyBasis(
+ ::Type{PT}, VD::Val{D}, ::Type{V}, order::Int, filter::Function=_q_filter) where {PT,D,V}
+
+ orders = tfill(order,VD)
+ UniformPolyBasis(PT,Val(D),V,orders,filter)
+end
+
+# API
+
+"""
+ get_exponents(b::UniformPolyBasis)
+
+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::UniformPolyBasis)
+ indexbase = 1
+ [Tuple(t) .- indexbase for t in b.terms]
+end
+
+"""
+ get_orders(b::UniformPolyBasis)
+
+Return the D-tuple of polynomial orders in each spatial dimension
+"""
+function get_orders(b::UniformPolyBasis)
+ b.orders
+end
+
+#################################
+# nD evaluations implementation #
+#################################
+
+function _evaluate_nd!(
+ b::UniformPolyBasis{D,V,K,PT}, x,
+ r::AbstractMatrix{V}, i,
+ c::AbstractMatrix{T}) where {D,V,K,PT,T}
+
+ terms = b.terms
+ orders = b.orders
+
+ for d in 1:D
+ Kd = Val(orders[d])
+ _evaluate_1d!(PT,Kd,c,x,d)
+ end
+
+ k = 1
+ for ci in terms
+
+ s = one(T)
+ for d in 1:D
+ @inbounds s *= c[d,ci[d]]
+ end
+
+ k = _uniform_set_value!(r,i,s,k)
+ end
+end
+
+"""
+ _uniform_set_value!(r::AbstractMatrix{<:Real},i,s,k)
+
+r[i,k] = s; return k+1
+"""
+function _uniform_set_value!(r::AbstractMatrix{<:Real},i,s,k)
+ @inbounds r[i,k] = s
+ k+1
+end
+
+"""
+ _uniform_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)`.
+"""
+function _uniform_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::UniformPolyBasis{D,V,K,PT}, x,
+ r::AbstractMatrix{G}, i,
+ c::AbstractMatrix{T},
+ g::AbstractMatrix{T},
+ s::MVector{D,T}) where {D,V,K,PT,G,T}
+
+ terms = b.terms
+ orders = b.orders
+
+ for d in 1:D
+ Kd = Val(orders[d])
+ _derivatives_1d!(PT,Kd,(c,g),x,d)
+ end
+
+ k = 1
+ for ci in terms
+
+ for i in eachindex(s)
+ @inbounds s[i] = one(T)
+ end
+
+ for q in 1:D
+ for d in 1:D
+ if d != q
+ @inbounds s[q] *= c[d,ci[d]]
+ else
+ @inbounds s[q] *= g[d,ci[d]]
+ end
+ end
+ end
+
+ k = _uniform_set_derivative!(r,i,s,k,V)
+ end
+end
+
+"""
+ _uniform_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 _uniform_set_derivative!(
+ r::AbstractMatrix{G},i,s,k,::Type{<:Real}) where G
+
+ @inbounds r[i,k] = s
+ k+1
+end
+
+"""
+ _uniform_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 _uniform_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 _uniform_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::UniformPolyBasis{D,V,K,PT}, x,
+ r::AbstractMatrix{G}, i,
+ c::AbstractMatrix{T},
+ g::AbstractMatrix{T},
+ h::AbstractMatrix{T},
+ s::MMatrix{D,D,T}) where {D,V,K,PT,G,T}
+
+ terms = b.terms
+ orders = b.orders
+
+ for d in 1:D
+ Kd = Val(orders[d])
+ _derivatives_1d!(PT,Kd,(c,g,h),x,d)
+ end
+
+ k = 1
+
+ for ci in terms
+
+ for i in eachindex(s)
+ @inbounds s[i] = one(T)
+ end
+
+ for t in 1:D
+ for q in 1:D
+ for d in 1:D
+ if d != q && d != t
+ @inbounds s[t,q] *= c[d,ci[d]]
+ elseif d == q && d ==t
+ @inbounds s[t,q] *= h[d,ci[d]]
+ else
+ @inbounds s[t,q] *= g[d,ci[d]]
+ end
+ end
+ end
+ end
+
+ k = _uniform_set_derivative!(r,i,s,k,V)
+ end
+end
+
diff --git a/src/ReferenceFEs/AWRefFEs.jl b/src/ReferenceFEs/AWRefFEs.jl
new file mode 100644
index 000000000..e73ddb54f
--- /dev/null
+++ b/src/ReferenceFEs/AWRefFEs.jl
@@ -0,0 +1,84 @@
+
+struct ArnoldWinther <: PushforwardRefFE end
+
+const arnoldwinther = ArnoldWinther()
+
+"""
+ struct ArnoldWinther <: PushforwardRefFE 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"
+ conforming = true # TODO: Make this an argument
+
+ VT = SymTensorValue{2,T}
+ prebasis = MonomialBasis(Val(2),VT,3,Polynomials._p_filter)
+ fb = MonomialBasis(Val(D-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(ArnoldWinther(),p,prebasis,moments,DivConformity())
+end
+
+function ReferenceFE(p::Polytope,::ArnoldWinther, order)
+ ArnoldWintherRefFE(Float64,p,order)
+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..156d5b35b 100644
--- a/src/ReferenceFEs/BDMRefFEs.jl
+++ b/src/ReferenceFEs/BDMRefFEs.jl
@@ -1,7 +1,9 @@
-struct BDM <: DivConforming end
+struct BDM <: PushforwardRefFE end
const bdm = BDM()
+Pushforward(::Type{<:BDM}) = ContraVariantPiolaMap()
+
"""
BDMRefFE(::Type{et},p::Polytope,order::Integer) where et
@@ -9,40 +11,34 @@ The `order` argument has the following meaning: the divergence of the functions
is in the P space of degree `order-1`.
"""
-function BDMRefFE(::Type{et},p::Polytope,order::Integer) where et
-
+function BDMRefFE(::Type{T},p::Polytope,order::Integer) where T
D = num_dims(p)
- vet = VectorValue{num_dims(p),et}
-
if is_simplex(p)
- prebasis = MonomialBasis(vet,p,order)
+ prebasis = MonomialBasis(Val(D),VectorValue{D,T},order,Polynomials._p_filter)
+ fb = MonomialBasis(Val(D-1),T,order,Polynomials._p_filter)
+ cb = PGradBasis(Monomial,Val(D),T,order-2)
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
+ 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 = GenericRefFE{BDM}(
- ndofs,
- p,
- prebasis,
- dof_basis,
- DivConformity(),
- metadata,
- face_dofs)
+ 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
- reffe
+ return MomentBasedReferenceFE(BDM(),p,prebasis,moments,DivConformity())
end
function ReferenceFE(p::Polytope,::BDM, order)
@@ -65,128 +61,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
-
- 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
+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..0273b5429 100644
--- a/src/ReferenceFEs/BezierRefFEs.jl
+++ b/src/ReferenceFEs/BezierRefFEs.jl
@@ -94,7 +94,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 ]
diff --git a/src/ReferenceFEs/CDLagrangianRefFEs.jl b/src/ReferenceFEs/CDLagrangianRefFEs.jl
index f4b071e08..f456ec9d9 100644
--- a/src/ReferenceFEs/CDLagrangianRefFEs.jl
+++ b/src/ReferenceFEs/CDLagrangianRefFEs.jl
@@ -136,7 +136,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..0cd4e5fc0 100644
--- a/src/ReferenceFEs/CLagrangianRefFEs.jl
+++ b/src/ReferenceFEs/CLagrangianRefFEs.jl
@@ -286,7 +286,7 @@ function _lagrangian_ref_fe(::Type{T},p::Polytope{D},orders) where {T,D}
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
@@ -369,9 +369,9 @@ function LagrangianRefFE(::Type{T},p::Polytope{D},order::Int;space::Symbol=_defa
LagrangianRefFE(T,p,orders;space=space)
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}
@@ -508,7 +508,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 +538,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,7 +594,7 @@ 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_own_nodes(p::ExtrusionPolytope{D},orders) where D
diff --git a/src/ReferenceFEs/CRRefFEs.jl b/src/ReferenceFEs/CRRefFEs.jl
new file mode 100644
index 000000000..c48874ac1
--- /dev/null
+++ b/src/ReferenceFEs/CRRefFEs.jl
@@ -0,0 +1,59 @@
+struct CR <: ReferenceFEName end
+const cr = CR()
+
+"""
+CRRefFE(::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 CRRefFE(::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 "CR 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_facet_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(CR(),p,prebasis,moments,L2Conformity())
+end
+
+
+function ReferenceFE(p::Polytope,::CR, order)
+ CRRefFE(Float64,p,order)
+end
+
+function ReferenceFE(p::Polytope,::CR,::Type{T}, order) where T
+ CRRefFE(T,p,order)
+end
+
+function Conformity(reffe::GenericRefFE{CR},sym::Symbol)
+ if sym == :L2
+ L2Conformity()
+ else
+ @unreachable """\n
+ It is not possible to use conformity = $sym on a CR reference FE.
+
+ Possible values of conformity for this reference fe are $((:L2, hdiv...)).
+ """
+ end
+end
+
+function get_face_own_dofs(reffe::GenericRefFE{CR}, conf::L2Conformity)
+ get_face_dofs(reffe)
+end
diff --git a/src/ReferenceFEs/Dofs.jl b/src/ReferenceFEs/Dofs.jl
index 75201f9b0..546afc1aa 100644
--- a/src/ReferenceFEs/Dofs.jl
+++ b/src/ReferenceFEs/Dofs.jl
@@ -1,73 +1,106 @@
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)
-
-# The following functions can be overloaded optionally
+"""
+ struct LinearCombinationDofVector{T<:Dof,V,F} <: AbstractVector{T}
+ values :: V
+ dofs :: F
+ end
-# - [`dof_return_type`](@ref)
+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 `dofs`) and the
+dof values are next mapped to dof basis (a) applying a change of basis (field
+`values`).
-# The interface is tested with
+Fields:
-# - [`test_dof`](@ref)
+- `values::AbstractMatrix{<:Number}` the matrix of the change from dof basis (b) to (a)
+- `dofs::AbstractVector{T}` A type representing dof basis (b), with `T<:Dof`
+"""
+struct LinearCombinationDofVector{T,V,F} <: AbstractVector{T}
+ values::V
+ dofs::F
+ function LinearCombinationDofVector(
+ values::AbstractMatrix{<:Number},
+ dofs::AbstractVector{<:Dof}
+ )
+ T = eltype(dofs)
+ V = typeof(values)
+ F = typeof(dofs)
+ new{T,V,F}(values,dofs)
+ end
+end
-# 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.
+Base.size(a::LinearCombinationDofVector) = (size(a.values,2),)
+Base.IndexStyle(::LinearCombinationDofVector) = IndexLinear()
+Base.getindex(::LinearCombinationDofVector{T},::Integer) where T = T()
+function linear_combination(a::AbstractMatrix{<:Number}, b::AbstractVector{<:Dof})
+ LinearCombinationDofVector(a,b)
+end
-# """
-# abstract type Dof <: Map end
+function return_cache(b::LinearCombinationDofVector,field)
+ k = Fields.LinearCombinationMap(:)
+ cf = return_cache(b.dofs,field)
+ fx = evaluate!(cf,b.dofs,field)
+ ck = return_cache(k,b.values,fx)
+ return cf, ck
+end
-# """
-# return_cache(dof,field)
+function evaluate!(cache,b::LinearCombinationDofVector,field)
+ cf, ck = cache
+ k = Fields.LinearCombinationMap(:)
+ fx = evaluate!(cf,b.dofs,field)
+ return evaluate!(ck,k,b.values,fx)
+end
-# Returns the cache needed to call `evaluate_dof!(cache,dof,field)`
-# """
-# function return_cache(dof::Dof,field)
-# @abstractmethod
-# end
+"""
+ struct MappedDofBasis{T<:Dof,MT,BT} <: AbstractVector{T}
+ F :: MT
+ σ :: BT
+ args
+ end
-# """
-# evaluate_dof!(cache,dof,field)
+Represents η = σ∘F, evaluated as η(φ) = σ(F(φ,args...))
-# 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).
+ - σ : V* -> R is a dof basis
+ - F : W -> V is a map between function spaces
-# 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
+Intended combinations would be:
-# """
-# dof_return_type(dof,field)
+- σ : V* -> R dof basis in the physical domain and F* : V̂ -> V is a pushforward map.
+- ̂σ : V̂* -> R dof basis in the reference domain and (F*)^-1 : V -> V̂ is an inverse pushforward map.
-# Returns the type for the value obtained with evaluating `dof` with `field`.
+"""
+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=(==))
@@ -93,17 +126,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..c7f3796d4 100644
--- a/src/ReferenceFEs/DuffyQuadratures.jl
+++ b/src/ReferenceFEs/DuffyQuadratures.jl
@@ -1,4 +1,14 @@
+"""
+ 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()
diff --git a/src/ReferenceFEs/HHJRefFEs.jl b/src/ReferenceFEs/HHJRefFEs.jl
new file mode 100644
index 000000000..9c0c691e5
--- /dev/null
+++ b/src/ReferenceFEs/HHJRefFEs.jl
@@ -0,0 +1,70 @@
+
+struct HellanHerrmannJhonson <: PushforwardRefFE end
+
+const hhj = HellanHerrmannJhonson()
+
+Pushforward(::Type{<:HellanHerrmannJhonson}) = DoubleContraVariantPiolaMap()
+
+"""
+ struct HellanHerrmannJhonson <: PushforwardRefFE 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
+ @assert p == TRI "HellanHerrmannJhonson Reference FE only defined for TRIangles"
+
+ VT = SymTensorValue{2,T}
+ prebasis = MonomialBasis{2}(VT,order,Polynomials._p_filter)
+ fb = MonomialBasis{D-1}(T,order,Polynomials._p_filter)
+ cb = MonomialBasis{2}(VT,order-1,Polynomials._p_filter)
+
+ 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
+ (get_dimrange(p,2),cmom,cb) # Cell moments
+ ]
+
+ return MomentBasedReferenceFE(HellanHerrmannJhonson(),p,prebasis,moments,DivConformity())
+end
+
+function ReferenceFE(p::Polytope,::HellanHerrmannJhonson, order)
+ HellanHerrmannJhonsonRefFE(Float64,p,order)
+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)
+ get_face_dofs(reffe)
+end
diff --git a/src/ReferenceFEs/LagrangianRefFEs.jl b/src/ReferenceFEs/LagrangianRefFEs.jl
index 0c6b738ba..28a903c63 100644
--- a/src/ReferenceFEs/LagrangianRefFEs.jl
+++ b/src/ReferenceFEs/LagrangianRefFEs.jl
@@ -340,10 +340,10 @@ 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}
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..2611d482f
--- /dev/null
+++ b/src/ReferenceFEs/MTWRefFEs.jl
@@ -0,0 +1,72 @@
+
+struct MardalTaiWinther <: PushforwardRefFE end
+
+const mtw = MardalTaiWinther()
+
+"""
+ struct MardalTaiWinther <: PushforwardRefFE 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"
+ @asset 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(MardalTaiWinther(),p,prebasis,moments,DivConformity())
+end
+
+function ReferenceFE(p::Polytope,::MardalTaiWinther, order)
+ MardalTaiWintherRefFE(Float64,p,order)
+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..631d6681d 100644
--- a/src/ReferenceFEs/ModalC0RefFEs.jl
+++ b/src/ReferenceFEs/ModalC0RefFEs.jl
@@ -150,7 +150,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 +181,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 +193,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/MomentBasedReferenceFEs.jl b/src/ReferenceFEs/MomentBasedReferenceFEs.jl
new file mode 100644
index 000000000..72abb3af9
--- /dev/null
+++ b/src/ReferenceFEs/MomentBasedReferenceFEs.jl
@@ -0,0 +1,373 @@
+
+# MomentBasedDofBasis
+
+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) = (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) = 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{P,V}, field) where {P,V}
+ alloc_cache(vals::AbstractVector,T,ndofs) = zeros(T,ndofs)
+ alloc_cache(vals::AbstractMatrix,T,ndofs) = zeros(T,ndofs,size(vals,2))
+ cf = return_cache(field,b.nodes)
+ vals = evaluate!(cf,field,b.nodes)
+ T = typeof(dot(zero(V),zero(eltype(vals))))
+ r = alloc_cache(vals,T,num_dofs(b))
+ 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
+
+# MomentBasedReferenceFE
+
+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
+
+# Extends a Df-dimensional vector to a Dc-dimensional one that
+# lives in the tangent space of the Dc-embedded Df-dimensional manifold.
+# Equivalent to transpose(∇(fmap))
+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
+
+# TO DO: Bug in 3D, when n==2; n==4 and D==3. Also, working on this to make better and more general.
+function get_facet_measure(p::Polytope{D}, face::Int) where D
+ measures = Float64[]
+ facet_entities = get_face_coordinates(p)
+ for entity in facet_entities
+ n = length(entity)
+ if n == 1
+ push!(measures, 0.0) # A point has zero measure
+ elseif n == 2
+ # Length of an edge
+ p1, p2 = entity
+ push!(measures, norm(p2-p1))
+ elseif n == 3 && D == 2
+ # Perimeter of the closed polygon
+ n = length(entity)
+ perimeter = 0.0
+ for i in 1:n
+ p1, p2 = entity[i], entity[mod1(i+1, n)] # cyclic indices
+ perimeter += norm([p2[1] - p1[1], p2[2] - p1[2]])
+ end
+ push!(measures, perimeter)
+ elseif n == 3 && D == 3
+ # Area of a simplex
+ p1, p2, p3 = entity
+ v1 = [p2[i] - p1[i] for i in 1:D]
+ v2 = [p3[i] - p1[i] for i in 1:D]
+ area = 0.5 * norm(cross(v1, v2))
+ push!(measures, area)
+ elseif n == 4 && D == 3
+ # Volume of a tetrahedron ( To do: Should be perimeter of the tetrahedron.)
+ p1, p2, p3, p4 = entity
+ v1 = [p2[i] - p1[i] for i in 1:D]
+ v2 = [p3[i] - p1[i] for i in 1:D]
+ v3 = [p4[i] - p1[i] for i in 1:D]
+ volume = abs(dot(v1, cross(v2, v3))) / 6
+ push!(measures, volume)
+ end
+
+ end
+ dim = get_dimranges(p)[face+1]
+ return measures[dim]
+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))
+ f = σ(φf,μ,ds)
+
+ 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,
+ σ::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_cache, detJ_cache, f_cache, xf, w = cache
+
+ fmap = ds.fmaps[ds.face]
+ φf = transpose(Broadcasting(Operation(∘))(φ,fmap))
+ f = σ(φf,μ,ds)
+
+ 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
+
+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
+
+"""
+A moment is given by a triplet (f,σ,μ) where
+ - f is vector of ids of faces Fk
+ - σ is a function σ(φ,μ,ds) that returns a Field-like object to be integrated over each Fk
+ - μ is a polynomials basis on Fk
+
+We are assuming that all the faces in a moment are of the same type.
+"""
+function MomentBasedReferenceFE(
+ name::ReferenceFEName,
+ p::Polytope{D},
+ prebasis::AbstractVector{<:Field},
+ moments::AbstractVector{<:Tuple},#{<:AbstractVector{Int},<:Function,<:AbstractVector{<:Field}}},
+ conformity::Conformity;
+) 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)
+
+ T = return_type(prebasis)
+ order = get_order(prebasis)
+ φ_vec = component_basis(T)
+ φ = map(constant_field,φ_vec)
+
+ # Create face measures for each moment
+ measures = Vector{FaceMeasure}(undef,n_moments)
+ for (k,(faces,σ,μ)) in enumerate(moments)
+ ftype = face_types[first(faces)]
+ @assert all(face_types[faces] .== ftype)
+ qdegree = order + get_order(μ) + 1
+ fp = reffaces[ftype]
+ measures[k] = FaceMeasure(p,fp,qdegree)
+ end
+
+ # Count number of dofs and quad pts per face
+ face_n_dofs = zeros(Int,n_faces)
+ face_n_nodes = zeros(Int,n_faces)
+ for ((faces,σ,μ),ds) in zip(moments,measures)
+ face_n_dofs[faces] .+= length(μ)
+ face_n_nodes[faces] .+= num_points(ds.quad)
+ end
+
+ # Compute face moment and node indices
+ n_dofs = 0
+ n_nodes = 0
+ face_own_dofs = Vector{Vector{Int}}(undef,n_faces)
+ face_nodes = Vector{UnitRange{Int}}(undef, n_faces)
+ face_moments = Vector{Array{T}}(undef, n_faces)
+ for face in 1:n_faces
+ n_dofs_i = face_n_dofs[face]
+ n_nodes_i = face_n_nodes[face]
+ face_own_dofs[face] = collect((n_dofs+1):(n_dofs+n_dofs_i))
+ face_nodes[face] = (n_nodes+1):(n_nodes+n_nodes_i)
+ face_moments[face] = zeros(T,n_nodes_i,n_dofs_i)
+ n_dofs += n_dofs_i
+ n_nodes += n_nodes_i
+ end
+
+ # Compute face moments and nodes
+ fill!(face_n_dofs,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)
+
+ dof_offset = face_n_dofs[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+dof_offset] = dot(vals[i,j,:],φ_vec)
+ end
+ nodes[i+node_offset] = coords[i]
+ end
+
+ face_n_nodes[face] += size(vals,1)
+ face_n_dofs[face] += size(vals,2)
+ end
+ end
+
+ dof_basis = MomentBasedDofBasis(nodes, face_moments, face_nodes)
+ metadata = nothing
+ return GenericRefFE{typeof(name)}(
+ n_dofs, p, prebasis, dof_basis, conformity, metadata, face_own_dofs
+ )
+end
diff --git a/src/ReferenceFEs/NedelecRefFEs.jl b/src/ReferenceFEs/NedelecRefFEs.jl
index c6c731ac9..5c46c07a3 100644
--- a/src/ReferenceFEs/NedelecRefFEs.jl
+++ b/src/ReferenceFEs/NedelecRefFEs.jl
@@ -1,9 +1,11 @@
-struct CurlConformity <: Conformity end
-struct Nedelec <: ReferenceFEName end
+struct CurlConformity <: Conformity end
+struct Nedelec <: PushforwardRefFE end
const nedelec = Nedelec()
+Pushforward(::Type{<:Nedelec}) = CoVariantPiolaMap()
+
"""
NedelecRefFE(::Type{et},p::Polytope,order::Integer) where et
@@ -11,40 +13,56 @@ The `order` argument has the following meaning: the curl of the functions in th
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{D}(et,order)
+ prebasis = QGradBasis(Monomial,Val(D),et,order) # Prebasis
+ eb = MonomialBasis(Val(1),et,order) # Edge basis
+ fb = QGradBasis(Monomial,Val(D-1),et,order-1) # Face basis
+ cb = QCurlGradBasis(Monomial,Val(D),et,order-1) # Cell basis
elseif is_simplex(p)
- prebasis = Polynomials.NedelecPrebasisOnSimplex{D}(order)
+ prebasis = PGradBasis(Monomial,Val(D),et,order) # Prebasis
+ eb = MonomialBasis(Val(1),et,order) # Edge basis
+ fb = MonomialBasis(Val(D-1),VectorValue{D-1,et},order-1,Polynomials._p_filter) # Face basis
+ cb = MonomialBasis(Val(D),VectorValue{D,et},order-D+1,Polynomials._p_filter) # Cell basis
else
- @unreachable "Only implemented for n-cubes and simplices"
+ @unreachable "Nedelec Reference FE 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
+ 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 = GenericRefFE{Nedelec}(
- ndofs,
- p,
- prebasis,
- dof_basis,
- CurlConformity(),
- metadata,
- face_dofs)
+ moments = Tuple[
+ (get_dimrange(p,1),emom,eb), # Edge moments
+ ]
+ if (D == 3) && order > 0
+ 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-2)
+ push!(moments,(get_dimrange(p,D),cmom,cb)) # Cell moments
+ end
- reffe
+ return MomentBasedReferenceFE(Nedelec(),p,prebasis,moments,CurlConformity())
end
function ReferenceFE(p::Polytope,::Nedelec, order)
@@ -70,33 +88,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)
+ 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 +117,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(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/Pullbacks.jl b/src/ReferenceFEs/Pullbacks.jl
new file mode 100644
index 000000000..1a825b460
--- /dev/null
+++ b/src/ReferenceFEs/Pullbacks.jl
@@ -0,0 +1,239 @@
+
+"""
+ 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
+
+abstract type PushforwardRefFE <: ReferenceFEName end
+
+Pushforward(::Type{<:PushforwardRefFE}) = @abstractmethod
+Pushforward(name::PushforwardRefFE) = Pushforward(typeof(name))
+
+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 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*)^-1 : 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**)^-1 : 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**)^-1(̂σ) := ̂σ∘(F*)^-1 : 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
+
+# ContraVariantPiolaMap
+
+struct ContraVariantPiolaMap <: Pushforward end
+
+function evaluate!(
+ cache, ::ContraVariantPiolaMap, v_ref::Number, Jt::Number
+)
+ idetJ = 1. / meas(Jt)
+ return v_ref ⋅ (idetJ * Jt)
+end
+
+function evaluate!(
+ 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 end
+
+function evaluate!(
+ cache, ::CoVariantPiolaMap, v_ref::Number, Jt::Number
+)
+ return v_ref ⋅ transpose(pinvJt(Jt))
+end
+
+function evaluate!(
+ cache, ::InversePushforward{CoVariantPiolaMap}, v_phys::Number, Jt::Number
+)
+ return v_phys ⋅ transpose(Jt)
+end
+
+# DoubleContraVariantPiolaMap
+
+struct DoubleContraVariantPiolaMap <: Pushforward end
+
+function evaluate!(
+ cache, ::DoubleContraVariantPiolaMap, v_ref::Number, Jt::Number
+)
+ _Jt = meas(Jt) * Jt
+ return transpose(_Jt) ⋅ v_ref ⋅ _Jt
+end
+
+function evaluate!(
+ cache, ::InversePushforward{DoubleContraVariantPiolaMap}, v_phys::Number, Jt::Number
+)
+ iJt = meas(Jt) * pinvJt(Jt)
+ return transpose(iJt) ⋅ v_phys ⋅ iJt
+end
+
+# DoubleCoVariantPiolaMap
+
+struct DoubleCoVariantPiolaMap <: Pushforward end
+
+function evaluate!(
+ cache, ::DoubleCoVariantPiolaMap, v_ref::Number, Jt::Number
+)
+ iJt = pinvJt(Jt)
+ return iJt ⋅ v_ref ⋅ transpose(iJt)
+end
+
+function evaluate!(
+ cache, ::InversePushforward{DoubleCoVariantPiolaMap}, v_phys::Number, Jt::Number
+)
+ return Jt ⋅ v_phys ⋅ transpose(Jt)
+end
diff --git a/src/ReferenceFEs/RaviartThomasRefFEs.jl b/src/ReferenceFEs/RaviartThomasRefFEs.jl
index d4388ea94..2af553b9a 100644
--- a/src/ReferenceFEs/RaviartThomasRefFEs.jl
+++ b/src/ReferenceFEs/RaviartThomasRefFEs.jl
@@ -1,60 +1,60 @@
-struct DivConformity <: Conformity end
-abstract type DivConforming <: ReferenceFEName end
+struct DivConformity <: Conformity end
-struct RaviartThomas <: DivConforming end
+# RaviartThomas
+struct RaviartThomas <: PushforwardRefFE end
const raviart_thomas = RaviartThomas()
+Pushforward(::Type{<:RaviartThomas}) = ContraVariantPiolaMap()
+
"""
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) where et
-
- D = num_dims(p)
+function RaviartThomasRefFE(
+ ::Type{T},p::Polytope{D},order::Integer
+) where {T,D}
if is_n_cube(p)
- prebasis = QCurlGradMonomialBasis{D}(et,order)
+ prebasis = QCurlGradBasis(Legendre,Val(D),T,order) # Prebasis
+ cb = QGradBasis(Legendre,Val(D),T,order-1) # Cell basis
+ fb = LegendreBasis(Val(D-1),T,order,Polynomials._q_filter) # Face basis
elseif is_simplex(p)
- prebasis = PCurlGradMonomialBasis{D}(et,order)
+ prebasis = PCurlGradBasis(Monomial,Val(D),T,order) # Prebasis
+ cb = LegendreBasis(Val(D),VectorValue{D,T},order-1,Polynomials._p_filter) # Cell basis
+ fb = LegendreBasis(Val(D-1),T,order,Polynomials._p_filter) # Face basis
else
- @notimplemented "H(div) Reference FE only available for cubes and simplices"
+ @notimplemented "Raviart-Thomas Reference FE only available for 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
+ 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 = GenericRefFE{RaviartThomas}(
- ndofs,
- p,
- prebasis,
- dof_basis,
- DivConformity(),
- metadata,
- face_dofs)
+ 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
- reffe
+ return MomentBasedReferenceFE(RaviartThomas(),p,prebasis,moments,DivConformity())
end
-function ReferenceFE(p::Polytope,::RaviartThomas, order)
- RaviartThomasRefFE(Float64,p,order)
+function ReferenceFE(p::Polytope,::RaviartThomas,order;kwargs...)
+ RaviartThomasRefFE(Float64,p,order;kwargs...)
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)
@@ -76,352 +76,16 @@ function get_face_own_dofs(reffe::GenericRefFE{RaviartThomas}, conf::DivConformi
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
+# TODO: Please remove me
+function legendreBasis(::Type{T},p::Polytope,orders) where T
+ compute_legendre_basis(T,p,orders)
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
+function legendreBasis(::Type{T},p::Polytope{D},order::Int) where {D,T}
+ orders = tfill(order,Val{D}())
+ legendreBasis(T,p,orders)
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)
+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
diff --git a/src/ReferenceFEs/ReferenceFEInterfaces.jl b/src/ReferenceFEs/ReferenceFEInterfaces.jl
index ead616c1f..a4b1c0b82 100644
--- a/src/ReferenceFEs/ReferenceFEInterfaces.jl
+++ b/src/ReferenceFEs/ReferenceFEInterfaces.jl
@@ -390,11 +390,11 @@ associated with the dof basis `predofs` and the 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))
+ change = transpose(inv(evaluate(predofs,shapefuns)))
linear_combination(change,predofs)
end
diff --git a/src/ReferenceFEs/ReferenceFEs.jl b/src/ReferenceFEs/ReferenceFEs.jl
index 428f183ea..289cdc256 100644
--- a/src/ReferenceFEs/ReferenceFEs.jl
+++ b/src/ReferenceFEs/ReferenceFEs.jl
@@ -1,7 +1,6 @@
"""
-The exported names are
-$(EXPORTS)
+$(public_names_in_md(@__MODULE__))
"""
module ReferenceFEs
@@ -19,7 +18,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 +32,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
@@ -56,6 +54,7 @@ export get_dimranges
export get_dimrange
export get_vertex_coordinates
export get_facet_normal
+export get_facet_measure
export get_facet_orientations
export get_edge_tangent
export get_vertex_permutations
@@ -182,9 +181,9 @@ export BDMRefFE
export NedelecRefFE
export BezierRefFE
export ModalC0RefFE
+export CRRefFE
export Lagrangian
-export DivConforming
export RaviartThomas
export BDM
export Nedelec
@@ -197,6 +196,7 @@ export bdm
export nedelec
export bezier
export modalC0
+export cr
export Quadrature
export QuadratureName
@@ -245,18 +245,22 @@ include("StrangQuadratures.jl")
include("XiaoGimbutasQuadratures.jl")
+include("Pullbacks.jl")
+
+include("MomentBasedReferenceFEs.jl")
+
include("RaviartThomasRefFEs.jl")
include("BDMRefFEs.jl")
include("NedelecRefFEs.jl")
+include("CRRefFEs.jl")
+
include("MockDofs.jl")
include("BezierRefFEs.jl")
include("ModalC0RefFEs.jl")
-include("LinearCombinationDofVectors.jl")
-
end # module
diff --git a/src/ReferenceFEs/SerendipityRefFEs.jl b/src/ReferenceFEs/SerendipityRefFEs.jl
index 935a72c0e..064e75553 100644
--- a/src/ReferenceFEs/SerendipityRefFEs.jl
+++ b/src/ReferenceFEs/SerendipityRefFEs.jl
@@ -26,7 +26,7 @@ println( num_dofs(reffe) )
function SerendipityRefFE(::Type{T},p::Polytope,order::Int) 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
@@ -87,12 +87,8 @@ 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
-end
-
function compute_monomial_basis(::Type{T},p::SerendipityPolytope{D},orders) where {T,D}
- MonomialBasis{D}(T,orders,_s_filter)
+ MonomialBasis(Val(D),T,orders,_ser_filter)
end
function compute_own_nodes(p::SerendipityPolytope{0},orders)
diff --git a/src/ReferenceFEs/StrangQuadratures.jl b/src/ReferenceFEs/StrangQuadratures.jl
index dc3054fe9..999640f16 100644
--- a/src/ReferenceFEs/StrangQuadratures.jl
+++ b/src/ReferenceFEs/StrangQuadratures.jl
@@ -1,5 +1,15 @@
+"""
+ 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()
function Quadrature(p::Polytope,::Strang,degree::Integer;T::Type{<:AbstractFloat}=Float64)
diff --git a/src/ReferenceFEs/TensorProductQuadratures.jl b/src/ReferenceFEs/TensorProductQuadratures.jl
index 2b47fbdfc..b8a731c62 100644
--- a/src/ReferenceFEs/TensorProductQuadratures.jl
+++ b/src/ReferenceFEs/TensorProductQuadratures.jl
@@ -1,4 +1,14 @@
+"""
+ 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::Union{Integer,NTuple{D,Integer}};T::Type{<:AbstractFloat}=Float64) where D
+"""
struct TensorProduct <: QuadratureName end
const tensor_product = TensorProduct()
diff --git a/src/ReferenceFEs/XiaoGimbutasQuadratures.jl b/src/ReferenceFEs/XiaoGimbutasQuadratures.jl
index 7fd185d28..21be9a1f3 100644
--- a/src/ReferenceFEs/XiaoGimbutasQuadratures.jl
+++ b/src/ReferenceFEs/XiaoGimbutasQuadratures.jl
@@ -1,18 +1,30 @@
+"""
+ struct XiaoGimbutas <: QuadratureName end
+
+Xiao-Gimbutas symmetric quadrature rule for simplices.
+
+# Constructor:
+
+ `Quadrature(p::Polytope,xiao_gimbutas::XiaoGimbutas,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()
-# 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 +36,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
diff --git a/src/ReferenceFEs/deprecated/BDMRefFEs.jl b/src/ReferenceFEs/deprecated/BDMRefFEs.jl
new file mode 100644
index 000000000..0a425b247
--- /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 = PGradBasis(Monomial,Val(num_dims(p)),et,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/TensorValues.jl b/src/TensorValues/TensorValues.jl
index 5d491f997..b46285e71 100644
--- a/src/TensorValues/TensorValues.jl
+++ b/src/TensorValues/TensorValues.jl
@@ -33,6 +33,8 @@ SymTensorValue( SMatrix{2}(1,2,3,4) ) # SymTensorValue{2, Int64, 3}(1, 3, 4)
```
See the official documentation for more details.
+
+$(public_names_in_md(@__MODULE__))
"""
module TensorValues
diff --git a/src/Visualization/Visualization.jl b/src/Visualization/Visualization.jl
index c20fb41ea..614ce8ee0 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/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 d68ec1cad..929b26556 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 2d2a569da..ac63b6f3f 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/FESpacesTests/CurlConformingFESpacesTests.jl b/test/FESpacesTests/CurlConformingFESpacesTests.jl
index bac081dd3..d5e983822 100644
--- a/test/FESpacesTests/CurlConformingFESpacesTests.jl
+++ b/test/FESpacesTests/CurlConformingFESpacesTests.jl
@@ -9,7 +9,7 @@ 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
@@ -27,7 +27,7 @@ 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
@@ -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)
@@ -128,7 +128,7 @@ e = u - uh
dΩ = Measure(Ω,order)
el2 = sqrt(sum( ∫( e⋅e )*dΩ ))
-@test el2 < 1.0e-10
+@test el2 < 2.0e-10
# using Gridap.Visualization
diff --git a/test/FESpacesTests/DivConformingFESpacesTests.jl b/test/FESpacesTests/DivConformingFESpacesTests.jl
index 8bda23e06..46dbd805d 100644
--- a/test/FESpacesTests/DivConformingFESpacesTests.jl
+++ b/test/FESpacesTests/DivConformingFESpacesTests.jl
@@ -10,19 +10,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 +30,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 +39,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
+#@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)
-
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)
@@ -87,7 +84,7 @@ end
reffe = ReferenceFE(TET,raviart_thomas,order)
- domain =(0,1,0,1,0,1)
+ domain = (0,1,0,1,0,1)
partition = (3,3,3)
model = simplexify(CartesianDiscreteModel(domain,partition))
@@ -103,7 +100,6 @@ end
v(x) = VectorValue(-0.5*x[1]+1.0,-0.5*x[2],-0.5*x[3])
vh = interpolate(v,V)
-
e = v - vh
Ω = Triangulation(model)
@@ -135,7 +131,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)
@@ -150,7 +146,7 @@ end
test_div_v_q_equiv(U,V,P,Q,Ω)
-end
+#end
@testset "Test BDM" begin
diff --git a/test/PolynomialsTests/BernsteinBasesTests.jl b/test/PolynomialsTests/BernsteinBasesTests.jl
new file mode 100644
index 000000000..60abe1ccd
--- /dev/null
+++ b/test/PolynomialsTests/BernsteinBasesTests.jl
@@ -0,0 +1,147 @@
+module BernsteinBasisTests
+
+using Test
+using Gridap.TensorValues
+using Gridap.Fields
+using Gridap.Polynomials
+using Gridap.Polynomials: binoms
+using ForwardDiff
+
+@test isHierarchical(Bernstein) == false
+
+np = 3
+x = [Point(0.),Point(1.),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)
+
+function test_internals(order,x,bx,∇bg,Hbx)
+ sz = (1,order+1)
+ for (i,xi) in enumerate(x)
+ v2 = zeros(sz)
+ Polynomials._evaluate_1d!(Bernstein,Val(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,Val(order),g2,xi,1)
+ @test all( [ bxi[1]≈vxi[1] for (bxi,vxi) in zip(∇bx[i,:],g2[:,1]) ] )
+
+ h2 = zeros(sz)
+ Polynomials._hessian_1d!(Bernstein,Val(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_orders(b) == (0,)
+
+bx = [ 1.; 1.; 1.;; ]
+∇bx = G[ 0.; 0.; 0.;; ]
+Hbx = H[ 0.; 0.; 0.;; ]
+
+test_internals(order,x,bx,∇bx,Hbx)
+
+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 = BernsteinBasis(Val(1),V,order)
+
+bx = [ 1.0 0.0
+ 0.0 1.0
+ 0.6 0.4]
+
+∇bx = G[ -1. 1.
+ -1. 1.
+ -1. 1. ]
+
+Hbx = H[ 0. 0.
+ 0. 0.
+ 0. 0. ]
+
+test_internals(order,x,bx,∇bx,Hbx)
+
+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 = BernsteinBasis(Val(1),V,order)
+
+bx = [ 1.0 0.0 0.0
+ 0.0 0.0 1.0
+ 0.36 0.48 0.16]
+
+
+∇bx = 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,∇bx,Hbx)
+
+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
+
+function bernstein(K,N)
+ b = binoms(Val(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)
+
+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 = [ bernstein(order,n)( xi[1]) for xi in x, n in 0:order]
+
+# -3x2+6x-3 9x2-12x+3 -9x2+6x 3x2
+∇bx = [ G(_∇(bernstein(order,n))(xi[1])) for xi in x, n in 0:order]
+
+# -6x+6 18x-12 -18x+6 6x
+Hbx = [ H(_H(bernstein(order,n))(xi[1])) for xi in x, n in 0:order]
+
+test_internals(order,x,bx,∇bx,Hbx)
+
+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 = BernsteinBasis(Val(1),V,order)
+
+bx = [ bernstein(order,n)( xi[1]) for xi in x, n in 0:order]
+∇bx = [ G(_∇(bernstein(order,n))(xi[1])) for xi in x, n in 0:order]
+Hbx = [ H(_H(bernstein(order,n))(xi[1])) for xi in x, n in 0:order]
+
+test_internals(order,x,bx,∇bx,Hbx)
+
+test_field_array(b,x,bx,≈, grad=∇bx,gradgrad=Hbx)
+test_field_array(b,x[1],bx[1,:],grad=∇bx[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..b32ddfd90
--- /dev/null
+++ b/test/PolynomialsTests/ChebyshevBasesTests.jl
@@ -0,0 +1,119 @@
+module ChebyshevBasisTests
+
+using Test
+using Gridap.TensorValues
+using Gridap.Fields
+using Gridap.Polynomials
+using Gridap.Polynomials: binoms
+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,)
+
+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/JacobiPolynomialBasesTests.jl b/test/PolynomialsTests/LegendreBasesTests.jl
similarity index 85%
rename from test/PolynomialsTests/JacobiPolynomialBasesTests.jl
rename to test/PolynomialsTests/LegendreBasesTests.jl
index fdbfe87fc..404492b77 100644
--- a/test/PolynomialsTests/JacobiPolynomialBasesTests.jl
+++ b/test/PolynomialsTests/LegendreBasesTests.jl
@@ -1,4 +1,4 @@
-module JacobiPolynomialBasisTests
+module LegendreBasisTests
using Test
using Gridap.TensorValues
@@ -6,6 +6,8 @@ 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 +19,24 @@ 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)
∇b2 = Broadcasting(∇)(b2)
∇∇b2 = Broadcasting(∇)(∇b2)
@@ -45,18 +47,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..3ba41a78e 100644
--- a/test/PolynomialsTests/ModalC0BasesTests.jl
+++ b/test/PolynomialsTests/ModalC0BasesTests.jl
@@ -4,6 +4,7 @@ using Test
using Gridap.TensorValues
using Gridap.Fields
using Gridap.Polynomials
+using StaticArrays
# using BenchmarkTools
import Gridap.Fields: Broadcasting
@@ -22,6 +23,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 +39,24 @@ 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 UniformPolyBasis
+
+order = 3
+a = fill(Point(0.),order+1)
+b = fill(Point(1.),order+1)
+b1 = ModalC0Basis{1}(V,order,a,b)
+b1u= UniformPolyBasis(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 +83,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 UniformPolyBasis
+
+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= UniformPolyBasis(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 e087d71b8..d3a734982 100644
--- a/test/PolynomialsTests/MonomialBasesTests.jl
+++ b/test/PolynomialsTests/MonomialBasesTests.jl
@@ -5,6 +5,10 @@ using Gridap.TensorValues
using Gridap.Fields
using Gridap.Polynomials
+using Gridap.Polynomials: _q_filter, _qs_filter, _p_filter, _ps_filter
+
+@test isHierarchical(Monomial) == true
+
xi = Point(2,3)
np = 5
x = fill(xi,np)
@@ -15,7 +19,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 get_order(b) == 0
@test get_orders(b) == (0,0)
@@ -35,7 +39,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)]
@@ -52,7 +56,7 @@ test_field_array(b,x[1],bx[1,:],grad=∇bx[1,:],gradgrad=Hbx[1,:])
orders = (1,2)
V = Float64
G = gradient_type(V,xi)
-b = MonomialBasis{2}(V,orders)
+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)]
@@ -68,7 +72,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],
@@ -107,7 +111,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),
@@ -133,7 +137,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]]
@@ -149,7 +153,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],
@@ -172,7 +176,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),
@@ -199,7 +203,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),
@@ -219,27 +223,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 _qs_filter( (1,2) ,order) == true
+@test _qs_filter( (2,0) ,order) == true
+@test _qs_filter( (2,2) ,order) == true
+@test _qs_filter( (1,1) ,order) == false
+@test _qs_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 _ps_filter( (1,2) ,order) == false
+@test _ps_filter( (2,0) ,order) == true
+@test _ps_filter( (2,2) ,order) == false
+@test _ps_filter( (1,1) ,order) == true
+@test _ps_filter( (3,1) ,order) == false
+
end # module
diff --git a/test/PolynomialsTests/PCurlGradBasesTests.jl b/test/PolynomialsTests/PCurlGradBasesTests.jl
new file mode 100644
index 000000000..07d171796
--- /dev/null
+++ b/test/PolynomialsTests/PCurlGradBasesTests.jl
@@ -0,0 +1,81 @@
+module PCurlGradBasesTests
+
+using Test
+using Gridap.TensorValues
+using Gridap.Fields
+using Gridap.Polynomials
+using Gridap.Arrays
+
+xi = Point(4,2)
+np = 1
+x = fill(xi,np)
+
+PT = Monomial
+
+order = 2
+D = 2
+T = Float64
+V = VectorValue{D,T}
+G = gradient_type(V,xi)
+b = PCurlGradBasis(PT, Val(D),T,order)
+
+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
+
+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 = PCurlGradBasis(PT, Val(D),T,order)
+
+@test length(b) == 15
+@test get_order(b) == 2
+
+
+# 1D
+
+order = 0
+D = 1
+T = Float64
+V = VectorValue{D,T}
+b = PCurlGradBasis(PT,Val(D),T,order)
+
+@test b isa UniformPolyBasis{D,V,order+1,PT}
+
+@test_throws AssertionError PCurlGradBasis(PT,Val(D),V,order)
+
+
+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/PGradBasesTests.jl b/test/PolynomialsTests/PGradBasesTests.jl
new file mode 100644
index 000000000..01138775d
--- /dev/null
+++ b/test/PolynomialsTests/PGradBasesTests.jl
@@ -0,0 +1,72 @@
+module PGradBasesTests
+
+using Test
+using Gridap.TensorValues
+using Gridap.Fields
+using Gridap.Arrays
+using Gridap.Polynomials
+
+xi = Point(2.,3.,5.)
+np = 3
+x = fill(xi,np)
+T = Float64
+
+D = 2
+for PT in (Legendre, Chebyshev, ModalC0, Bernstein)
+ @test_throws ErrorException PGradBasis(PT,Val(D),T,0)
+end
+
+PT = Monomial
+
+order = 0
+D = 3
+b = NedelecPolyBasisOnSimplex{D}(PT, T, 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)]
+
+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)
+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
+D = 1
+T = Float64
+V = VectorValue{D,T}
+b = PGradBasis(PT,Val(D),T,order)
+
+@test b isa UniformPolyBasis{D,V,order+1,PT}
+
+@test_throws AssertionError PGradBasis(PT,Val(D),V,order)
+
+# D > 3 not implemented
+
+@test_throws ErrorException NedelecPolyBasisOnSimplex{4}(PT, T, order)
+
+
+end # module
diff --git a/test/PolynomialsTests/PolynomialInterfacesTests.jl b/test/PolynomialsTests/PolynomialInterfacesTests.jl
new file mode 100644
index 000000000..7d4660761
--- /dev/null
+++ b/test/PolynomialsTests/PolynomialInterfacesTests.jl
@@ -0,0 +1,81 @@
+module ChangeBasisTests
+
+using Test
+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)
+
+# 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, Val(1), c, xi, 1)
+@test_throws ErrorException Polynomials._gradient_1d!(MockPolynomial, Val(1), c, xi, 1)
+@test_throws ErrorException Polynomials._hessian_1d!( MockPolynomial, Val(1), c, xi, 1)
+@test_throws ErrorException Polynomials._derivatives_1d!(MockPolynomial, Val(1), (nothing,nothing,nothing,nothing), xi, 1)
+
+function Polynomials._evaluate_1d!(
+ ::Type{MockPolynomial},::Val{K}, cc::AbstractMatrix{T}, xi, d) where {K,T<:Number}
+
+ cc[1,1] = 1.
+end
+Polynomials._derivatives_1d!(MockPolynomial, Val(1), (c,), xi, 1)
+@test c[1][1] == 1.
+
+###########################
+# PolynomialBasis interface
+###########################
+
+T = Float64
+D = 1
+struct MockPolyBasis <: PolynomialBasis{D,T,0,MockPolynomial} end
+
+mb = MockPolyBasis()
+
+
+# Implemented interfaces
+@test IndexStyle(mb) == IndexLinear()
+@test return_type(mb) == T
+@test get_order(mb) == 0
+@test mb[1] == MockPolynomial()
+
+
+# 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)
+
+∇mb = FieldGradientArray{1}(mb)
+r, c, g = return_cache(∇mb,x)
+s = MVector{D,T}(0.)
+
+@test_throws ErrorException Polynomials._gradient_nd!(mb, xi, r, 1, c, g, s)
+
+Hmb = FieldGradientArray{2}(mb)
+r, c, g, h = return_cache(Hmb,x)
+s = MMatrix{D,D,T}(0.)
+
+@test_throws ErrorException Polynomials._hessian_nd!(mb, xi, r, 1, c, g, h, s)
+
+
+end
diff --git a/test/PolynomialsTests/QCurlGradMonomialBasesTests.jl b/test/PolynomialsTests/QCurlGradBasesTests.jl
similarity index 52%
rename from test/PolynomialsTests/QCurlGradMonomialBasesTests.jl
rename to test/PolynomialsTests/QCurlGradBasesTests.jl
index a2c31a787..30a7d3207 100644
--- a/test/PolynomialsTests/QCurlGradMonomialBasesTests.jl
+++ b/test/PolynomialsTests/QCurlGradBasesTests.jl
@@ -1,4 +1,4 @@
-module QCurlGradMonomialBasesTests
+module QCurlGradBasesTests
using Test
using Gridap.TensorValues
@@ -9,15 +9,19 @@ xi = Point(2,3)
np = 5
x = fill(xi,np)
+PT = Monomial
+
order = 0
D = 2
T = Float64
V = VectorValue{D,T}
G = gradient_type(V,xi)
-b = QCurlGradMonomialBasis{D}(T,order)
+b = QCurlGradBasis(PT,Val(D),T,order)
+
+@test length(b) == 4
+@test get_order(b) == 1
-@test num_terms(b) == 4
-@test get_order(b) == 0
+@test_throws AssertionError QCurlGradBasis(PT,Val(D),V,order)
xi = Point(2,3,5)
np = 5
@@ -28,18 +32,19 @@ D = 3
T = Float64
V = VectorValue{D,T}
G = gradient_type(V,xi)
-b = QCurlGradMonomialBasis{D}(T,order)
+b = QCurlGradBasis(PT, Val(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)]
+ (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),
- (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 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, 0.0, 0.0, 0.0, 0.0),
+ (0.0, 0.0, 0.0, 0.0, 0.0, 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)
@@ -56,24 +61,42 @@ D = 2
T = Float64
V = VectorValue{D,T}
G = gradient_type(V,xi)
-b = QCurlGradMonomialBasis{D}(T,order)
+b = QCurlGradBasis(PT, Val(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)]
+ ( 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, 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)]
+ ( 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
+T = Float64
+V = VectorValue{D,T}
+b = QCurlGradBasis(PT,Val(D),T,order)
+
+@test b isa UniformPolyBasis{D,V,order+1,PT}
+
+@test_throws AssertionError QCurlGradBasis(PT,Val(D),V,order)
+
+
end # module
diff --git a/test/PolynomialsTests/QGradBasesTests.jl b/test/PolynomialsTests/QGradBasesTests.jl
new file mode 100644
index 000000000..1a7d0efd1
--- /dev/null
+++ b/test/PolynomialsTests/QGradBasesTests.jl
@@ -0,0 +1,171 @@
+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)
+PT = Monomial
+
+# 3D
+order = 0
+D = 2
+T = Float64
+V = VectorValue{D,T}
+G = gradient_type(V,xi)
+b = QGradBasis(PT, Val(D),T,order)
+
+@test length(b) == 4
+@test get_order(b) == 1
+
+@test_throws AssertionError QGradBasis(PT,Val(D),V,order)
+
+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)
+H = gradient_type(G,xi)
+b = QGradBasis(PT, Val(D),T,order)
+
+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
+D = 2
+T = Float64
+V = VectorValue{D,T}
+G = gradient_type(V,xi)
+H = gradient_type(G,xi)
+b = QGradBasis(PT, Val(D),T,order)
+
+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
+D = 1
+T = Float64
+V = VectorValue{D,T}
+b = QGradBasis(PT,Val(D),T,order)
+
+@test b isa UniformPolyBasis{D,V,order+1,PT}
+
+@test_throws AssertionError QGradBasis(PT,Val(D),V,order)
+
+# Misc
+
+# Derivatives not implemented for symetric tensor types
+
+T = Float64
+V = SymTensorValue{D,T}
+G = gradient_type(V,xi)
+r = zeros(G, (1,1))
+@test_throws ErrorException Polynomials._comp_wize_set_derivative!(r,0,0,0,V)
+
+T = Float64
+V = SymTracelessTensorValue{D,T}
+G = gradient_type(V,xi)
+r = zeros(G, (1,1))
+@test_throws ErrorException Polynomials._comp_wize_set_derivative!(r,0,0,0,V)
+
+end # module
diff --git a/test/PolynomialsTests/QGradMonomialBasesTests.jl b/test/PolynomialsTests/QGradMonomialBasesTests.jl
deleted file mode 100644
index 90f4cbdd1..000000000
--- a/test/PolynomialsTests/QGradMonomialBasesTests.jl
+++ /dev/null
@@ -1,125 +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
-
-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)]
-
-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 813a48417..2c25b3ae4 100644
--- a/test/PolynomialsTests/runtests.jl
+++ b/test/PolynomialsTests/runtests.jl
@@ -2,17 +2,25 @@ 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 "QGradBases" begin include("QGradBasesTests.jl") end
+
+@testset "QCurlGradBases" begin include("QCurlGradBasesTests.jl") end
-@testset "QCurlGradMonomialBases" begin include("QCurlGradMonomialBasesTests.jl") end
+@testset "PGradBases" begin include("PGradBasesTests.jl") end
-@testset "PCurlGradMonomialBases" begin include("PCurlGradMonomialBasesTests.jl") end
+@testset "PCurlGradBases" begin include("PCurlGradBasesTests.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 "ChangeBasis" begin include("ChangeBasisTests.jl") end
diff --git a/test/ReferenceFEsTests/BDMRefFEsTests.jl b/test/ReferenceFEsTests/BDMRefFEsTests.jl
index f695d2c19..7b124ae18 100644
--- a/test/ReferenceFEsTests/BDMRefFEsTests.jl
+++ b/test/ReferenceFEsTests/BDMRefFEsTests.jl
@@ -1,4 +1,4 @@
-module BDMRefFEsTest
+# module BDMRefFEsTest
using Test
using Gridap.Polynomials
@@ -34,7 +34,7 @@ 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)
+@enter test_dof_array(dof_basis,field,r)
cache = return_cache(dof_basis,prebasis)
r = evaluate!(cache, dof_basis, prebasis)
@@ -98,4 +98,4 @@ reffe = ReferenceFE(TET,bdm,Float64,1)
@test BDM() == bdm
-end # module
+# end # module
diff --git a/test/ReferenceFEsTests/BezierRefFEsTests.jl b/test/ReferenceFEsTests/BezierRefFEsTests.jl
index 483cdfad8..c92052858 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 =
[
diff --git a/test/ReferenceFEsTests/CLagrangianRefFEsTests.jl b/test/ReferenceFEsTests/CLagrangianRefFEsTests.jl
index f2c6204f0..4dc8dee38 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)]
diff --git a/test/ReferenceFEsTests/CRRefFEsTests.jl b/test/ReferenceFEsTests/CRRefFEsTests.jl
new file mode 100644
index 000000000..24869c0d8
--- /dev/null
+++ b/test/ReferenceFEsTests/CRRefFEsTests.jl
@@ -0,0 +1,33 @@
+using Gridap
+using Gridap.ReferenceFEs
+using Gridap.Geometry
+using Gridap.Fields
+using Gridap.Arrays
+using Gridap.ReferenceFEs
+using Gridap.Polynomials
+using Gridap.Helpers
+
+using FillArrays
+
+
+p = TRI
+D = num_dims(p)
+
+# T = Float64
+T = VectorValue{D,Float64}
+
+
+cr_reffe = CRRefFE(T,p,1)
+
+cr_dofs = get_dof_basis(cr_reffe)
+cr_prebasis = get_prebasis(cr_reffe)
+cr_shapefuns = get_shapefuns(cr_reffe)
+
+M = evaluate(cr_dofs, cr_shapefuns)
+
+partition = (0,1,0,1)
+cells = (2,2)
+model = simplexify(CartesianDiscreteModel(partition, cells))
+
+V = FESpace(model,cr_reffe)
+get_cell_dof_ids(V)
\ No newline at end of file
diff --git a/test/ReferenceFEsTests/CrouziexRaviartTests.jl b/test/ReferenceFEsTests/CrouziexRaviartTests.jl
new file mode 100644
index 000000000..ed867bc3a
--- /dev/null
+++ b/test/ReferenceFEsTests/CrouziexRaviartTests.jl
@@ -0,0 +1,148 @@
+module CrouziexRaviartTests
+
+ using Gridap
+ using Gridap.ReferenceFEs, Gridap.Geometry, Gridap.FESpaces, Gridap.Arrays, Gridap.TensorValues
+ using Gridap.Helpers
+
+
+ function solve_crScalarPoisson(partition, cells, u_exact)
+
+ f(x) = - Δ(u_exact)(x)
+
+ model = simplexify(CartesianDiscreteModel(partition, cells))
+
+ # reffe = CRRefFE(VectorValue{2,Float64},TRI,1)
+ reffe = CRRefFE(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 = CRRefFE(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 = CRRefFE(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)
+ 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))")
+
+end # module
+
+
+
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/NedelecRefFEsTests.jl b/test/ReferenceFEsTests/NedelecRefFEsTests.jl
index 1e0db175c..99ccb68e9 100644
--- a/test/ReferenceFEsTests/NedelecRefFEsTests.jl
+++ b/test/ReferenceFEsTests/NedelecRefFEsTests.jl
@@ -15,8 +15,8 @@ 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()
@@ -28,9 +28,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)
@@ -53,8 +53,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,8 +94,8 @@ 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)
@@ -114,14 +114,14 @@ 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()
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()
@@ -137,31 +137,31 @@ 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]]
-
+
#display(face_odofs)
using Gridap.Geometry
diff --git a/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl b/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl
index 1bd40ca7b..92ea581c9 100644
--- a/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl
+++ b/test/ReferenceFEsTests/RaviartThomasRefFEsTests.jl
@@ -21,10 +21,12 @@ 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()
+
+
p = QUAD
D = num_dims(QUAD)
et = Float64
@@ -32,9 +34,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)
@@ -78,11 +80,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 +93,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,14 +114,14 @@ 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 length(get_prebasis(reffe)) == 4
+@test get_order(get_prebasis(reffe)) == 1
@test num_dofs(reffe) == 4
@test Conformity(reffe) == DivConformity()
reffe = ReferenceFE(QUAD,raviart_thomas,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) == DivConformity()
diff --git a/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl b/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl
index 6830d6835..c1aae9fc3 100644
--- a/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl
+++ b/test/ReferenceFEsTests/ReferenceFEInterfacesTests.jl
@@ -8,7 +8,7 @@ using Gridap.ReferenceFEs
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/TensorValuesTests/TypesTests.jl b/test/TensorValuesTests/TypesTests.jl
index bb1dfd073..44845c697 100644
--- a/test/TensorValuesTests/TypesTests.jl
+++ b/test/TensorValuesTests/TypesTests.jl
@@ -117,7 +117,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}()
@@ -681,13 +681,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)
@@ -695,12 +695,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)
diff --git a/test/moment_based_reffes.jl b/test/moment_based_reffes.jl
new file mode 100644
index 000000000..dcfc20462
--- /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{D}(Float64,order)
+cb = QGradJacobiPolynomialBasis{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{D}(Float64,order)
+cb = QCurlGradMonomialBasis{D}(Float64,order-1)
+fb = QGradMonomialBasis{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(),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{D}(Float64,order)
+cb = QCurlGradMonomialBasis{D}(Float64,order-1)
+fb = QGradMonomialBasis{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(),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