Draft: Unified transformed local finite elements
Motivation
dune-Functions currently needs several different kinds of local finite-element transformations:
- Scalar bases such as Lagrange keep their values unchanged, but derivatives must be transformed from reference to physical coordinates.
- Raviart-Thomas, Brezzi-Douglas-Marini, and Nedelec elements transform their vector-valued ranges with contravariant or covariant Piola maps. Their natural derivatives are divergence and curl.
- Cubic Hermite, Morley, and Argyris elements are not affine-equivalent and need an element-dependent transformation of the complete basis-function set.
- More complex elements may need several transformations in sequence and may change scalar, vector, or tensor range types.
These cases were previously implemented through unrelated wrappers, mixins, and element-specific finite elements. This merge request introduces one experimental framework for describing them while retaining element-specific transformation details in the corresponding basis headers.
The framework also replaces the fixed value/Jacobian interface with derivative
tags. A finite element only implements the quantities that are mathematically
meaningful for it, for example Derivatives::Divergence for an H(div) element
or Derivatives::Curl for an H(curl) element.
Central abstractions
TransformedLocalFiniteElement
TransformedLocalFiniteElement is the common finite-element adapter. It binds:
- A reference local finite element.
- An element-dependent context.
- A transformation policy for physical-basis evaluation.
- Optionally, the physical-function pullback provided by the same policy.
It provides:
referenceBasis()for the untransformed reference basis.physicalBasis()for values and derivatives on the physical element.localBasis(), selected throughLocalBasisMode, for compatibility with existing basis-node conventions.- The reference local coefficients.
localInterpolation(), selected independently throughInterpolationMode.precomputeIdentity()for caches that must distinguish reference finite elements, polynomial orders, and orientation variants.
LocalBasisMode::reference is used where existing callers expect
localBasis() to retain the classical reference-element meaning, notably for
scalar Lagrange-like bases. LocalBasisMode::physical is used for elements
whose traditional local basis is already the mapped physical basis.
InterpolationMode::reference exposes the reference interpolation and is the
default. InterpolationMode::transformed uses
Transformation::localFunctionPullback() before invoking the reference
interpolation. Basis and interpolation selection are independent: there is no
separate interpolation-transformation type. In transformed mode, one policy
constructor argument initializes independent basis and interpolation adapters,
which are then bound to the same context.
The default precomputation identity uses the address of the bound reference
finite element. A reference finite element can provide its own
precomputeIdentity() returning LocalBasisPrecomputeIdentity, including a
generation counter when the same storage can be rebuilt with different
contents.
TransformedLocalBasis
The transformed basis exposes derivative-dependent types and operations:
template<class Derivative>
using PrecomputeBuffer = ...;
template<class Derivative>
using DerivativeRange = ...;
precompute(derivative, x, buffer);
finalize(derivative, x, buffer, values);
evaluate(derivative, x, values);The transformation policy determines which derivative tags are available and the physical result type of each derivative. The current tag vocabulary is:
ValueJacobianGradientDivergenceCurlHessianLaplacian
The combined evaluate() operation is the convenient user interface.
precompute() and finalize() form the cache-oriented interface.
Bind contexts
A transformation is bound to a context rather than directly to a geometry. Reusable contexts include:
ElementBindContextGeometryBindContextSimplexVertexMeshSizeContextSimplexEdgeOrientationContextSimplexVertexMeshSizeAndEdgeOrientationContext
The basic contexts provide a stable geometry snapshot. Richer contexts add element information required by non-affine-equivalent elements, such as edge orientations and per-vertex mesh sizes.
Transformation classes
Reference evaluation
ReferenceLocalBasisEvaluator evaluates the mathematical operator named by a
derivative tag on the reference element:
- values use
evaluateFunction(); - Jacobians use
evaluateJacobian(); - gradient, divergence, and curl use dedicated local-basis methods when available and otherwise derive the requested operator from reference Jacobians;
- Hessians use
evaluateHessian()when available and otherwise use second partial derivatives; - Laplacians use
evaluateLaplacian()when available and otherwise take the trace of reference Hessians.
ReferenceEvaluation separates the requested physical derivative from the
reference operator required by a transformation. Its operator policy normally
keeps the derivative unchanged, but may remap it. For example, a physical
Laplacian requires the complete reference Hessian. This also allows an element
author to provide a custom reference evaluator or operator-selection policy
without modifying the pipeline.
Piola transformations request reference values and their natural reference operators directly: divergence for contravariant Piola and curl for covariant Piola. They do not evaluate a complete reference Jacobian merely to extract the natural derivative.
Ordered evaluation pipelines
BasisEvaluationPipeline applies an ordered sequence of typed transformation
stages after reference evaluation. Each stage declares its output type as a
function of the derivative tag, local basis, context, and input range. Stages
may therefore change scalar, vector, or tensor dimensions.
Two reusable stage adapters distinguish the main transformation classes:
BasisSetTransformationStagetransforms the complete vector of basis functions and may mix basis-function indices.RangeTransformationStagetransforms each basis-function result independently and may change its range type.
The selected reference operator is evaluated in precompute(). Intermediate
stage vectors are stored in the caller-owned precompute buffer and are reused
between finalizations. The cached reference evaluation remains immutable.
Geometry derivative transformation
GeometryDerivativeTransformation is a pointwise range transformation for
scalar physical derivatives. GeometryDerivativeStage adapts it for use in a
pipeline.
It implements:
- unchanged scalar values;
- physical Jacobians;
- ambient tangential gradients on embedded geometries;
- affine, codimension-zero Hessians and Laplacians.
Non-affine Hessians and Laplacians are deliberately rejected. Their complete chain rule needs higher derivatives of the geometry that are not readily available through the current geometry interface.
Piola transformations
ContravariantPiolaTransformation implements H(div) values, divergence, and
the corresponding interpolation pullback.
CovariantPiolaTransformation implements H(curl) values, curl, and the
corresponding interpolation pullback.
Both support rectangular geometry Jacobians and separate reference and physical range types. Piola transformations remain complete transformation policies rather than pipeline stages because reference operator selection, natural derivative mapping, and interpolation pullback are tightly coupled.
Element-specific basis-set transformations
The transformation formulas for cubic Hermite, Morley, and Argyris remain in
their respective basis headers. They are wrapped in
BasisSetTransformationStage and composed with
GeometryDerivativeStage.
This keeps specialized finite-element mathematics close to the element while reusing the common binding, evaluation, derivative, and caching infrastructure.
Implementing another transformed finite element
A finite-element author provides:
- A reference local finite element with basis, coefficients, and interpolation.
- A bind context containing the geometry and any additional element data, such as orientations or vertex mesh sizes.
- A basis transformation policy with derivative-dependent
PrecomputeBufferandDerivativeRangetypes,bind(),precompute(), andfinalize(). For staged transformations, this is normally aBasisEvaluationPipeline. - If physical functions must be pulled back for interpolation, the same
transformation policy additionally provides
localFunctionPullback()and the adapter selectsInterpolationMode::transformed. Otherwise the defaultInterpolationMode::referenceexposes the reference interpolation. - A
LocalBasisModeselecting whether compatibilitylocalBasis()denotes the reference or physical basis.
Pointwise transformations should normally be implemented as a policy wrapped
in RangeTransformationStage. Transformations mixing basis-function indices
should use BasisSetTransformationStage. Piola-like mappings whose natural
reference operator and interpolation pullback are inseparable can remain
complete transformation policies.
For example, a Piola finite element uses one transformation type for both operations:
using FiniteElement = TransformedLocalFiniteElement<
ReferenceFiniteElement,
Context,
PiolaTransformation,
LocalBasisMode::physical,
InterpolationMode::transformed>;Scalar derivative and basis-set transformations normally keep the default reference interpolation.
If several reference finite elements can coexist, for example orientation or hp variants, their precomputation identities must differ. The default reference finite-element address provides this distinction while the owning finite-element map remains unchanged. Rebuilt storage can provide an explicit generation-aware identity.
Migrated finite elements
The following bases use the new framework:
| Transformation family | Finite elements |
|---|---|
| Scalar physical derivatives | Lagrange, refined Lagrange, Rannacher-Turek |
| Contravariant Piola | Raviart-Thomas, Brezzi-Douglas-Marini |
| Covariant Piola | Nedelec |
| Double-Contravariant Piola | Hellan-Hermann-Johnson !596 |
| Basis-set transformation followed by geometry derivative transformation | Cubic Hermite, Morley, Argyris |
The discrete global-basis derivative implementation now detects a physical basis and evaluates its physical Jacobian directly. Conventional finite elements retain the previous reference-Jacobian chain-rule path.
Caching
Reference-basis evaluation is independent of the current physical geometry. The API therefore separates evaluation into:
precompute(): evaluate and retain the required reference quantity in a buffer that also owns reusable pipeline scratch storage.finalize(): apply the transformations that depend on the bound context.
QuadratureBasisFunctionCache demonstrates this protocol:
- It stores precompute buffers independently for every basis-tree leaf and derivative tag.
- A cache key combines
LocalBasisPrecomputeIdentitywith the address of the quadrature-rule object. Equality is checked on the complete key, so hash collisions cannot produce incorrect evaluations. - Rules returned by
QuadratureRules::rule()reside in global storage and have stable process-lifetime addresses. Other quadrature-rule objects must outlive the cache. - The default basis identity is the bound reference finite-element address, distinguishing RT, BDM, and Nedelec orientation variants as well as independently stored hp variants.
- Precomputations are stored in an
unordered_mapper derivative tag. - It rebinds cached leaf entries to the current local-view nodes.
- It reuses reference evaluations only when both the reference finite-element identity and quadrature-rule identity match.
- It repeats finalization for every bound physical element.
clear()explicitly invalidates precomputations and bound-node references. It must be called after updating a basis unless custom generation-aware identities are provided.- It exposes
basis(),size(), andorder()as a small assembly-facing facade.
For multi-stage transformations, intermediate vectors are part of the
precompute buffer. This avoids allocating temporary vectors in every
finalize() call while preserving the reference part of the buffer as
immutable.
Tests and compatibility
The transformed-basis tests use derivative tags and compare supported derivatives with finite-difference approximations. Additional coverage includes:
- scalar tangential gradients on embedded geometries;
- covariant and contravariant surface Piola maps;
- divergence and curl transformations;
- Piola interpolation duality;
- composed basis-set and geometry transformations;
- reusable pipeline scratch storage;
- explicit rejection of non-affine Hessians;
- hybrid triangle/quadrilateral quadrature caching;
- heterogeneous derivative result types in composite bases;
- distinct cache entries for Piola orientation variants;
- explicit cache invalidation and reuse after
clear(); - rebinding after local-view destruction;
- physical derivatives of discrete Argyris, Morley, and cubic Hermite functions.
The transformed-basis-benchmark example assembles an RT mass plus
divergence-divergence form and compares manual reference evaluation and Piola
mapping, combined evaluate(), split precompute()/finalize(), and
quadrature-cache evaluation. All paths are checked against the same assembly
checksum. It also contains an experimental vector-backed cache lookup for
comparison with the installed unordered_map implementation.
The classical localBasis() and localInterpolation() behavior is preserved
per basis through LocalBasisMode and InterpolationMode. A constrained
compatibility evaluateJacobian() remains available for affine,
codimension-zero Piola elements without adding a generic Piola Jacobian
derivative tag.