|
ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
|
#include <ERF_NodalReconstruction.H>

Public Types | |
| using | Real = amrex::Real |
Public Member Functions | |
| NodalReconstruction (amrex::Box const &face_box, amrex::Geometry const &geom) | |
| amrex::Box const & | nodalBox () const noexcept |
| amrex::FArrayBox | makeReference (amrex::FArrayBox const &T) const |
| std::pair< amrex::FArrayBox, SolveInfo > | solve (amrex::FArrayBox const &T, amrex::FArrayBox const &reference, VariationOperator var_op, Real relative_tolerance=Real(1.e-10), Real smoothing=Real(1.e-2), Real min_regularization=Real(2.5e-4), int max_iterations=1000) |
| Real | maxAverageError (amrex::FArrayBox const &T, amrex::FArrayBox const &S) const |
| max_ij | avg4(S) - T | More... | |
| Real | totalSquaredVariation (amrex::FArrayBox const &S) const |
Private Member Functions | |
| void | applyA (amrex::FArrayBox const &S, amrex::FArrayBox &T) const |
| (A S)(i,j) = 1/4 [ S(i,j) + S(i+1,j) + S(i,j+1) + S(i+1,j+1) ] More... | |
| void | applyAT (amrex::FArrayBox const &T, amrex::FArrayBox &R) const |
| Adjoint of applyA: scatter each cell value to its four nodes. More... | |
| void | applyD (amrex::FArrayBox const &S, amrex::FArrayBox &difx, amrex::FArrayBox &dify) const |
| void | applyDT (amrex::FArrayBox const &difx, amrex::FArrayBox const &dify, amrex::FArrayBox &R) const |
| void | applyDTD (amrex::FArrayBox const &S, amrex::FArrayBox &R) const |
| void | applyL (amrex::FArrayBox const &S, amrex::FArrayBox &lap) const |
| void | applyLT (amrex::FArrayBox const &lap, amrex::FArrayBox &R) const |
| void | applyLTL (amrex::FArrayBox const &S, amrex::FArrayBox &R) const |
| void | applyVariationOperator (amrex::FArrayBox const &S, amrex::FArrayBox &R) const |
| void | applyM (amrex::FArrayBox const &S, amrex::FArrayBox &R) const |
| R = ( A^T A + lambda V^T V + mu I ) S. More... | |
| void | computeDiagonal (amrex::FArrayBox &diag) const |
| void | computeDiagnostics (amrex::FArrayBox const &T, amrex::FArrayBox const &S, SolveInfo &info) const |
| Real | maxAbs (amrex::FArrayBox const &F) const |
| SolveInfo | conjugateGradient (amrex::FArrayBox const &rhs, amrex::FArrayBox &x, Real relative_tolerance, int max_iterations) const |
| void | applyJacobi (amrex::FArrayBox const &diag, amrex::FArrayBox const &r, amrex::FArrayBox &z) const |
| Real | dot (amrex::FArrayBox const &a, amrex::FArrayBox const &b) const |
Private Attributes | |
| amrex::Box | m_face_box |
| amrex::Box | m_nodal_box |
| int | m_ilo =0 |
| int | m_ihi =-1 |
| int | m_jlo =0 |
| int | m_jhi =-1 |
| amrex::Real | m_gx = one |
| amrex::Real | m_gy = one |
| amrex::Real | m_gxx = one |
| amrex::Real | m_gyy = one |
| amrex::Real | m_lambda = zero |
| amrex::Real | m_mu = zero |
| VariationOperator | m_var_op = VariationOperator::FirstDeriv |
| amrex::FArrayBox | m_cc_scratch |
| amrex::FArrayBox | m_nd_scratch |
Static Private Attributes | |
| static constexpr amrex::Real | deviation_factor = amrex::Real(2.0) |
| static constexpr amrex::Real | relief_factor = amrex::Real(0.25) |
| ... nor than this fraction of the relief of the data. More... | |
| static constexpr amrex::Real | mu_growth = amrex::Real(4.0) |
| static constexpr int | max_attempts = 24 |
Reconstruct a nodal field S from data T that lives at cell centers in (x,y), such that the four-node average of S matches T as closely as a bounded, smooth nodal field can.
The four-node averaging operator A,
(A S)(i,j) = 1/4 [ S(i,j) + S(i+1,j) + S(i,j+1) + S(i+1,j+1) ] ,
has symbol cos(kx/2) cos(ky/2), which vanishes at the grid Nyquist mode. Inverting A exactly – e.g. with the back substitution
S(i+1,j+1) = 4 T(i,j) - S(i,j) - S(i+1,j) - S(i,j+1)
– therefore amplifies grid-scale content of T without bound. The Green's function of that recursion is 4 / [(1+x)(1+y)], a doubly alternating cumulative sum, so a checkerboard component of T of amplitude a produces a nodal response of amplitude a*i*j. On a 128^2 grid an O(10 m) grid-scale roughness in the WRF terrain thus yields nodal heights that are kilometers away from the WRF terrain, including physically impossible negative elevations (ERF issue #3709). Adding a free homogeneous solution cannot repair this: the null space of A is spanned by (-1)^i f(j) + (-1)^j g(i), which cannot represent the (-1)^(i+j) i j growth of the particular solution. The failure is silent, because checking that avg4(S) reproduces T is then satisfied by construction.
Let S_ref be the direct interpolation of T to the nodes (makeReference) and R = T - A S_ref the part of T that interpolation fails to reproduce. We solve for a correction E to the interpolant,
min_E || A E - R ||^2 + lambda || V E ||^2 + mu || E ||^2 , S = S_ref + E,
where V is a roughness operator (first derivative or Laplacian). The normal equations
( A^T A + lambda V^T V + mu I ) E = A^T R
are symmetric positive definite for any mu > 0, so E exists, is unique, and – unlike the exact inverse – is bounded. Specifically, writing ( A^T A + lambda V^T V + mu I ) E = A^T R and pairing with E gives
|| A E ||^2 + lambda || V E ||^2 + mu || E ||^2 <= || A E || || R || ,
and since 2 sqrt(mu) ||A E|| ||E|| <= ||A E||^2 + mu ||E||^2, we get the a priori bound
|| E || <= || R || / ( 2 sqrt(mu) ) .
The correction is therefore controlled by how badly interpolation already fails, never by the conditioning of A.
A single fixed mu cannot serve both smooth and rough terrain: small mu recovers well-resolved features almost exactly but lets rough fields drift far from the interpolant, and large mu does the reverse. We therefore start from a small mu and raise it until the correction satisfies the pointwise cap
max |E| <= min( 2 max|R| , 1/4 (max T - min T) ) ,
i.e. the nodes may not move away from the direct interpolation by more than twice the interpolation misfit, nor by more than a quarter of the relief of the data. Because ||E|| -> 0 as mu -> infinity the loop always terminates, and on failure we fall back to the interpolant itself. Smooth terrain keeps the smallest mu and is reproduced to well below the interpolation error; rough terrain is pulled back toward the interpolant instead of blowing up.
The averaging error max |avg4(S) - T| returned in SolveInfo is now a genuine diagnostic – it is no longer satisfied by construction – and is reported alongside the same quantity for the plain interpolant and the range of S, so callers can sanity check the result.
| using NodalReconstruction::Real = amrex::Real |
|
inlineexplicit |
|
inlineprivate |
|
inlineprivate |
|
inlineprivate |
|
inlineprivate |
|
inlineprivate |
Referenced by applyVariationOperator().


|
inlineprivate |
|
inlineprivate |
|
inlineprivate |
|
inlineprivate |
Referenced by applyVariationOperator().


|
inlineprivate |
R = ( A^T A + lambda V^T V + mu I ) S.
Referenced by conjugateGradient().


|
inlineprivate |
Referenced by applyM().


|
inlineprivate |
Referenced by solve().


|
inlineprivate |
Diagonal of M, assembled by accumulating the square of every stencil coefficient that touches a node. Used as a Jacobi preconditioner.
Referenced by conjugateGradient().

|
inlineprivate |
Jacobi-preconditioned conjugate gradient on the SPD operator M. x is both the initial guess and the result; it is reset to zero here because every attempt restarts the correction from the interpolant.
Referenced by solve().


|
inlineprivate |
|
inline |
Direct interpolation of the cell-centered data to the nodes: the average of the (up to four) cells that touch each node, with edge and corner nodes taking the average of the cells that exist. This is the field the solve corrects, and the field it falls back to.
|
inlineprivate |
|
inline |
max_ij | avg4(S) - T |
Referenced by computeDiagnostics().

|
inlinenoexcept |
|
inline |
Solve the regularized least-squares problem described above.
| T | cell-centered data to be matched |
| reference | the interpolant to correct (see makeReference) |
| var_op | roughness operator used as the smoothness prior |
| relative_tolerance | CG stopping tolerance |
| smoothing | lambda; weight of the roughness penalty |
| min_regularization | smallest mu tried; raised by factors of four until the correction respects its cap |
| max_iterations | CG iteration cap per attempt |

|
inline |
|
staticconstexprprivate |
The correction may not move a node further from the direct interpolation than this multiple of the interpolation misfit ...
Referenced by solve().
|
mutableprivate |
|
private |
Referenced by solve().
|
private |
Referenced by applyD(), applyDT(), computeDiagonal(), and NodalReconstruction().
|
private |
Referenced by applyL(), applyLT(), computeDiagonal(), and NodalReconstruction().
|
private |
Referenced by applyD(), applyDT(), computeDiagonal(), and NodalReconstruction().
|
private |
Referenced by applyL(), applyLT(), computeDiagonal(), and NodalReconstruction().
|
private |
Referenced by applyA(), applyAT(), applyD(), applyDT(), applyJacobi(), applyL(), applyLT(), applyM(), computeDiagnostics(), computeDiagonal(), conjugateGradient(), dot(), makeReference(), maxAbs(), maxAverageError(), solve(), and totalSquaredVariation().
|
private |
Referenced by applyA(), applyAT(), applyD(), applyDT(), applyJacobi(), applyL(), applyLT(), applyM(), computeDiagnostics(), computeDiagonal(), conjugateGradient(), dot(), makeReference(), maxAbs(), maxAverageError(), solve(), and totalSquaredVariation().
|
private |
Referenced by applyA(), applyAT(), applyD(), applyDT(), applyJacobi(), applyL(), applyLT(), applyM(), computeDiagnostics(), computeDiagonal(), conjugateGradient(), dot(), makeReference(), maxAbs(), maxAverageError(), solve(), and totalSquaredVariation().
|
private |
Referenced by applyA(), applyAT(), applyD(), applyDT(), applyJacobi(), applyL(), applyLT(), applyM(), computeDiagnostics(), computeDiagonal(), conjugateGradient(), dot(), makeReference(), maxAbs(), maxAverageError(), solve(), and totalSquaredVariation().
|
private |
Referenced by applyM(), computeDiagonal(), and solve().
|
private |
Referenced by applyM(), computeDiagonal(), and solve().
|
mutableprivate |
|
private |
Referenced by applyDTD(), applyLTL(), conjugateGradient(), makeReference(), nodalBox(), NodalReconstruction(), and solve().
|
private |
Referenced by applyVariationOperator(), computeDiagonal(), and solve().
|
staticconstexprprivate |
Referenced by solve().
|
staticconstexprprivate |
Factor by which mu is raised when the cap is violated, and the number of times we are willing to do so.
Referenced by solve().
|
staticconstexprprivate |
... nor than this fraction of the relief of the data.
Referenced by solve().