Modal (Legendre spectral-decay) refinement indicator for 2-D spectral element solutions.
Provides the trigger mechanism for adaptive mesh refinement (AMR): a per-element scalar that measures how much of a solution field's energy sits in its highest polynomial modes. A well-resolved (smooth) field has energy that decays rapidly with mode number, so almost all of its energy lives in the low modes; an under-resolved field (a steep front, a discontinuity, or simply too-coarse an element) leaves significant energy in the top modes. Comparing the top-mode energy fraction against thresholds flags each element for refinement, coarsening, or no change.
Following Persson & Peraire (2006), "Sub-cell shock capturing for discontinuous Galerkin methods", AIAA 2006-112, and the robustified variant of Hennemann, Rueda-Ramirez, Hindenlang & Gassner (2021), J. Comput. Phys. 426, 109935 (the shock indicator used in Trixi.jl), the nodal solution on an element is expanded in a tensor-product Legendre modal basis
u(xi,eta) = sum_{p=0}^{N} sum_{q=0}^{N} uhat(p,q) Ltilde_p(xi) Ltilde_q(eta),
where Ltilde_p are the L2-normalized Legendre polynomials on [-1,1] (int_{-1}^{1} Ltilde_p Ltilde_q dxi = delta_pq). With that normalization the total modal energy equals the exact L2 energy of the field on the reference element,
E_tot = sum_{p,q} uhat(p,q)^2 = ||u||_{L2([-1,1]^2)}^2 .
Two "clipped" energies are formed by dropping the highest one and two modes in each direction,
E_clip1 = sum_{p,q <= N-1} uhat(p,q)^2 , E_clip2 = sum_{p,q <= N-2} uhat(p,q)^2 ,
and the smoothness ratio is the larger of the top-shell and next-shell energy fractions,
S_e = max( (E_tot - E_clip1)/E_tot , (E_clip1 - E_clip2)/E_clip1 ) .
The second term (present only for N >= 2) guards against the odd/even parity dropouts that a single-mode measure can suffer for symmetric data - this is the Hennemann-Gassner refinement of the original single-mode Persson-Peraire estimate. The indicator returned per element is the base-10 logarithm sigma_e = log10(S_e); a near-machine-zero floor keeps it finite for a perfectly resolved (or identically zero) field.
With user-supplied thresholds sigma_refine > sigma_coarsen the per-element flag is
sigma_e > sigma_refine -> SELF_AMR_REFINE (+1) top modes carry too much energy sigma_e < sigma_coarsen -> SELF_AMR_COARSEN (-1) field is over-resolved on this element otherwise -> SELF_AMR_KEEP (0)
Larger (closer to zero) sigma_e means a less smooth / less resolved field. Recommended starting values for double precision are sigma_refine ~ -3.0 and sigma_coarsen ~ -8.0; they are problem dependent and are deliberately left as required arguments rather than hidden defaults.
This module contains the portable (CPU) implementation using do concurrent over elements. The backend extension modules (SELF_RefinementIndicator_2D) add device storage and a GPU kernel while preserving the mathematics bit-for-bit in structure.
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer, | public, | parameter | :: | SELF_AMR_ALLVARS | = | 0 | |
| integer, | public, | parameter | :: | SELF_AMR_COARSEN | = | -1 | |
| integer, | public, | parameter | :: | SELF_AMR_KEEP | = | 0 | |
| integer, | public, | parameter | :: | SELF_AMR_REFINE | = | 1 |
| Type | Visibility | Attributes | Name | Initial | |||
|---|---|---|---|---|---|---|---|
| integer, | public | :: | N | = | 0 | Polynomial degree of the interpolant the indicator is built for. |
|
| real(kind=prec), | public, | pointer, contiguous, dimension(:,:) | :: | Pmodal | => | null() | Nodal-to-modal transform. Pmodal(ii,p) is the (p,ii) entry of the inverse Legendre Vandermonde in the L2-normalized basis, so the 1-D modal coefficients are uhat(p) = sum_ii Pmodal(ii,p) * u(ii) (first index summed, SELF matrix convention). |
| real(kind=prec), | public | :: | coarsenThreshold | = | 0.0_prec | Elements with sigma_e below this value are flagged SELF_AMR_COARSEN. |
|
| integer, | public, | pointer, contiguous, dimension(:) | :: | flag | => | null() | Per-element refinement flag: SELF_AMR_REFINE / SELF_AMR_KEEP / SELF_AMR_COARSEN. |
| real(kind=prec), | public, | pointer, contiguous, dimension(:) | :: | indicator | => | null() | Per-element indicator value sigma_e = log10(S_e). |
| integer, | public | :: | nElem | = | 0 | Number of (rank-local) elements the indicator arrays are sized for. |
|
| real(kind=prec), | public | :: | refineThreshold | = | 0.0_prec | Elements with sigma_e above this value are flagged SELF_AMR_REFINE. |
| procedure, public :: CountFlagged => CountFlagged_RefinementIndicator2D_t | |
| procedure, public :: Estimate => Estimate_RefinementIndicator2D_t | |
| procedure, public :: Free => Free_RefinementIndicator2D_t | |
| procedure, public :: Init => Init_RefinementIndicator2D_t | |
| procedure, public :: SetThresholds => SetThresholds_RefinementIndicator2D_t | |
| procedure, public :: UpdateDevice => UpdateDevice_RefinementIndicator2D_t | |
| procedure, public :: UpdateHost => UpdateHost_RefinementIndicator2D_t |
Count the rank-local elements currently carrying the requested flag value (SELF_AMR_REFINE / SELF_AMR_KEEP / SELF_AMR_COARSEN). Provided as a convenience for drivers and tests; a global count across MPI ranks is the caller's responsibility.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(RefinementIndicator2D_t), | intent(in) | :: | this | |||
| integer, | intent(in) | :: | flagValue |
L2-normalized Legendre polynomial Ltilde_p(x) = L_p(x)*sqrt((2p+1)/2) on [-1,1], evaluated with the standard three-term recurrence. The normalization gives int_{-1}^{1} Ltilde_p Ltilde_q dx = delta_pq.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| integer, | intent(in) | :: | p | |||
| real(kind=real64), | intent(in) | :: | x |
Build the nodal->modal transform Pmodal for a 1-D degree-N interpolant whose nodes are controlPoints(1:N+1). The transform is the exact inverse of the L2-normalized Legendre Vandermonde V(i,p) = Ltilde_{p-1}(x_i); Pmodal(ii,p) stores V^{-1}(p,ii) so that uhat(p) = sum_ii Pmodal(ii,p) * u(ii). The Vandermonde inverse is formed in double precision for conditioning and cast back to the working precision.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=prec), | intent(in) | :: | controlPoints(1:N+1) | |||
| integer, | intent(in) | :: | N | |||
| real(kind=prec), | intent(out) | :: | Pmodal(1:N+1,1:N+1) |
Compute the per-element modal-energy indicator sigma_e and refine/keep/coarsen flag from the nodal solution field. ivar selects the driving variable in [1,solution%nVar]; passing SELF_AMR_ALLVARS (=0) reduces the indicator over all variables by taking, per element, the largest (least smooth) smoothness ratio S_e before the log10.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(RefinementIndicator2D_t), | intent(inout) | :: | this | |||
| class(Scalar2D), | intent(in) | :: | solution | |||
| integer, | intent(in) | :: | ivar |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(RefinementIndicator2D_t), | intent(inout) | :: | this |
Allocate the indicator for an interpolant of degree interp%N and nElem elements and precompute the nodal->modal transform matrix from the interpolant control points.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(RefinementIndicator2D_t), | intent(out) | :: | this | |||
| type(Lagrange), | intent(in), | target | :: | interp | ||
| integer, | intent(in) | :: | nElem | |||
| real(kind=prec), | intent(in) | :: | refineThreshold | |||
| real(kind=prec), | intent(in) | :: | coarsenThreshold |
Invert the n-by-n matrix A by Gauss-Jordan elimination with partial pivoting, in double precision. Used once at initialization for the small (N+1) Legendre Vandermonde; A is a well-conditioned Vandermonde in an orthonormal basis, so a direct solve is appropriate.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=real64), | intent(in) | :: | A(1:n,1:n) | |||
| real(kind=real64), | intent(out) | :: | Ainv(1:n,1:n) | |||
| integer, | intent(in) | :: | n |
Update the refine/coarsen thresholds without rebuilding the transform.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(RefinementIndicator2D_t), | intent(inout) | :: | this | |||
| real(kind=prec), | intent(in) | :: | refineThreshold | |||
| real(kind=prec), | intent(in) | :: | coarsenThreshold |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(RefinementIndicator2D_t), | intent(inout) | :: | this |
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(RefinementIndicator2D_t), | intent(inout) | :: | this |