ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
NodalReconstruction Class Reference

#include <ERF_NodalReconstruction.H>

Collaboration diagram for NodalReconstruction:

Public Types

using Real = amrex::Real
 

Public Member Functions

 NodalReconstruction (amrex::Box const &w_face_slice_box, amrex::Geometry const &geom)
 Construct a nodal reconstruction object for one horizontal slice. More...
 
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 w-face value to its four nodes. More...
 
void applyD (amrex::FArrayBox const &S, amrex::FArrayBox &difx, amrex::FArrayBox &dify) const
 Compute the first derivatives of the nodal field. More...
 
void applyDT (amrex::FArrayBox const &difx, amrex::FArrayBox const &dify, amrex::FArrayBox &R) const
 Apply the adjoint of the derivative operator. More...
 
void applyDTD (amrex::FArrayBox const &S, amrex::FArrayBox &R) const
 Apply the product of the derivative operator and its adjoint. More...
 
void applyL (amrex::FArrayBox const &S, amrex::FArrayBox &lap) const
 Apply the Laplacian operator to a nodal field. More...
 
void applyLT (amrex::FArrayBox const &lap, amrex::FArrayBox &R) const
 Apply the adjoint of the Laplacian operator. More...
 
void applyLTL (amrex::FArrayBox const &S, amrex::FArrayBox &R) const
 Apply the product of the Laplacian operator and its adjoint. More...
 
void applyVariationOperator (amrex::FArrayBox const &S, amrex::FArrayBox &R) const
 Apply the selected variation operator (first derivative or Laplacian). More...
 
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
 Compute diagnostic metrics for the reconstructed nodal field. More...
 
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
 Apply the Jacobi preconditioner. More...
 
Real dot (amrex::FArrayBox const &a, amrex::FArrayBox const &b) const
 

Private Attributes

amrex::Box m_w_face_slice_box
 
amrex::Box m_nodal_box
 
amrex::Box m_x_edge_box
 
amrex::Box m_y_edge_box
 
amrex::Box m_interior_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_w_face_scratch
 
amrex::FArrayBox m_nd_scratch
 
amrex::FArrayBox m_difx
 
amrex::FArrayBox m_dify
 
amrex::FArrayBox m_lap
 

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
 

Detailed Description

Reconstruct a nodal field S from data T that lives at w-faces, such that the four-node average of S matches T as closely as a bounded, smooth nodal field can.


Why we do not simply invert the averaging operator

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.


What we do instead

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.


Choosing mu

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.

Member Typedef Documentation

◆ Real

using NodalReconstruction::Real = amrex::Real

Constructor & Destructor Documentation

◆ NodalReconstruction()

NodalReconstruction::NodalReconstruction ( amrex::Box const &  w_face_slice_box,
amrex::Geometry const &  geom 
)
inlineexplicit

Construct a nodal reconstruction object for one horizontal slice.

The operator that is inverted is the purely horizontal four-node average, so the only thing that matters about the input is that it carries the w-face staggering; whether the heights come from the wrfinput geopotential at a z-face or from the metgrid surface field HGT_M is irrelevant here.

Parameters
w_face_slice_boxBox specifying the data region: w-face staggered, a single slab at k = 0.
geomGeometry used for grid spacing and anisotropy weights.
148  : m_w_face_slice_box(w_face_slice_box),
149  m_ilo(w_face_slice_box.smallEnd(0)),
150  m_ihi(w_face_slice_box.bigEnd(0)),
151  m_jlo(w_face_slice_box.smallEnd(1)),
152  m_jhi(w_face_slice_box.bigEnd(1))
153  {
154  // These are amrex::Abort rather than throw: an uncaught exception under
155  // MPI is std::terminate, which tells the user nothing about what failed.
156  if (!w_face_slice_box.ok()) {
157  amrex::Abort("NodalReconstruction: w_face_slice_box is empty; the input "
158  "heights were probably never read.");
159  }
160  if (w_face_slice_box.length(2) != 1) {
161  amrex::Abort("NodalReconstruction: w_face_slice_box must contain exactly one z cell.");
162  }
163  if (w_face_slice_box.smallEnd(2) != 0) {
164  amrex::Abort("NodalReconstruction: w_face_slice_box must be a slab at k = 0; "
165  "all fabs are indexed at k = 0.");
166  }
167 
168  m_nodal_box = amrex::surroundingNodes(w_face_slice_box);
169  m_nodal_box.setSmall(2, 0);
170  m_nodal_box.setBig(2, 0);
171 
172  // Scratch for the operators applied once per CG iteration, sized once
173  // here rather than allocated inside every matrix-vector product.
174  m_x_edge_box = m_nodal_box; m_x_edge_box.growHi(0,-1);
175  m_y_edge_box = m_nodal_box; m_y_edge_box.growHi(1,-1);
177  m_interior_nodal_box.grow(0,-1);
178  m_interior_nodal_box.grow(1,-1);
179 
180  Real const dx = geom.CellSizeArray()[0];
181  Real const dy = geom.CellSizeArray()[1];
182  if (!(dx > zero) || !(dy > zero)) {
183  amrex::Abort("NodalReconstruction: cell sizes must be positive.");
184  }
185 
186  // Non-dimensional, O(1) stencil weights that respect grid anisotropy.
187  // They are unity on an isotropic grid, so the smoothing weight has the
188  // same meaning regardless of the mesh spacing.
189  Real const h = std::min(dx,dy);
190  m_gx = h / dx;
191  m_gy = h / dy;
192  m_gxx = m_gx * m_gx;
193  m_gyy = m_gy * m_gy;
194  }
const Real dy
Definition: ERF_InitCustomPert_ABL.H:45
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
amrex::Real Real
Definition: ERF_ShocInterface.H:19
amrex::Real m_gyy
Definition: ERF_NodalReconstruction.H:409
amrex::Box m_x_edge_box
Definition: ERF_NodalReconstruction.H:399
int m_ihi
Definition: ERF_NodalReconstruction.H:403
int m_ilo
Definition: ERF_NodalReconstruction.H:402
amrex::Real m_gxx
Definition: ERF_NodalReconstruction.H:408
amrex::Box m_y_edge_box
Definition: ERF_NodalReconstruction.H:400
amrex::Box m_nodal_box
Definition: ERF_NodalReconstruction.H:398
amrex::Box m_interior_nodal_box
Definition: ERF_NodalReconstruction.H:401
int m_jlo
Definition: ERF_NodalReconstruction.H:404
amrex::Real m_gx
Definition: ERF_NodalReconstruction.H:406
amrex::Box m_w_face_slice_box
Definition: ERF_NodalReconstruction.H:397
amrex::Real m_gy
Definition: ERF_NodalReconstruction.H:407
int m_jhi
Definition: ERF_NodalReconstruction.H:405

Member Function Documentation

◆ applyA()

void NodalReconstruction::applyA ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  T 
) const
inlineprivate

(A S)(i,j) = 1/4 [ S(i,j) + S(i+1,j) + S(i,j+1) + S(i+1,j+1) ]

Apply the four-node averaging operator A.

Parameters
[in]SNodal field to be averaged.
[out]TResulting w-face field.
430  {
431  auto const S_arr = S.const_array();
432  auto const T_arr = T.array();
433  for (int j=m_jlo; j<=m_jhi; ++j) {
434  for (int i=m_ilo; i<=m_ihi; ++i) {
435  T_arr(i,j,0) = fourth * ( S_arr(i ,j ,0) + S_arr(i+1,j ,0)
436  + S_arr(i ,j+1,0) + S_arr(i+1,j+1,0) );
437  }
438  }
439  }
constexpr amrex::Real fourth
Definition: ERF_NumericalConstants.H:42
@ T
Definition: ERF_IndexDefines.H:131
constexpr int S
Definition: ERF_TwoStreamColumn.H:604

Referenced by applyM(), and solve().

Here is the caller graph for this function:

◆ applyAT()

void NodalReconstruction::applyAT ( amrex::FArrayBox const &  T,
amrex::FArrayBox &  R 
) const
inlineprivate

Adjoint of applyA: scatter each w-face value to its four nodes.

Apply the adjoint of the averaging operator A.

Parameters
[in]TW-face field to be scattered.
[out]RResulting nodal field.
449  {
450  R.setVal<amrex::RunOn::Host>(zero);
451  auto const T_arr = T.const_array();
452  auto const R_arr = R.array();
453  for (int j=m_jlo; j<=m_jhi; ++j) {
454  for (int i=m_ilo; i<=m_ihi; ++i) {
455  Real const q = fourth * T_arr(i,j,0);
456  R_arr(i ,j ,0) += q;
457  R_arr(i+1,j ,0) += q;
458  R_arr(i ,j+1,0) += q;
459  R_arr(i+1,j+1,0) += q;
460  }
461  }
462  }
@ R
Definition: ERF_IndexDefines.H:133
@ q
Definition: ERF_WSM6.H:273

Referenced by applyM(), and solve().

Here is the caller graph for this function:

◆ applyD()

void NodalReconstruction::applyD ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  difx,
amrex::FArrayBox &  dify 
) const
inlineprivate

Compute the first derivatives of the nodal field.

Parameters
[in]SNodal field.
[out]difxResulting x-derivatives.
[out]difyResulting y-derivatives.
473  {
474  difx.setVal<amrex::RunOn::Host>(zero);
475  dify.setVal<amrex::RunOn::Host>(zero);
476 
477  auto const S_arr = S.const_array();
478  auto const difx_arr = difx.array();
479  auto const dify_arr = dify.array();
480 
481  for (int j = m_jlo; j <= m_jhi + 1; ++j) {
482  for (int i = m_ilo; i <= m_ihi; ++i) {
483  difx_arr(i, j, 0) = m_gx * ( S_arr(i+1, j, 0) - S_arr(i, j, 0) );
484  }
485  }
486 
487  for (int j = m_jlo; j <= m_jhi; ++j) {
488  for (int i = m_ilo; i <= m_ihi + 1; ++i) {
489  dify_arr(i, j, 0) = m_gy * ( S_arr(i, j+1, 0) - S_arr(i, j, 0) );
490  }
491  }
492  }

Referenced by applyDTD().

Here is the caller graph for this function:

◆ applyDT()

void NodalReconstruction::applyDT ( amrex::FArrayBox const &  difx,
amrex::FArrayBox const &  dify,
amrex::FArrayBox &  R 
) const
inlineprivate

Apply the adjoint of the derivative operator.

Parameters
[in]difxX-derivatives.
[in]difyY-derivatives.
[out]RResulting nodal field.
503  {
504  R.setVal<amrex::RunOn::Host>(zero);
505 
506  auto const qx_arr = difx.const_array();
507  auto const qy_arr = dify.const_array();
508  auto const R_arr = R.array();
509 
510  for (int j = m_jlo; j <= m_jhi + 1; ++j) {
511  for (int i = m_ilo; i <= m_ihi; ++i) {
512  R_arr(i , j, 0) -= m_gx * qx_arr(i, j, 0);
513  R_arr(i+1, j, 0) += m_gx * qx_arr(i, j, 0);
514  }
515  }
516 
517  for (int j = m_jlo; j <= m_jhi; ++j) {
518  for (int i = m_ilo; i <= m_ihi + 1; ++i) {
519  R_arr(i, j , 0) -= m_gy * qy_arr(i, j, 0);
520  R_arr(i, j+1, 0) += m_gy * qy_arr(i, j, 0);
521  }
522  }
523  }

Referenced by applyDTD().

Here is the caller graph for this function:

◆ applyDTD()

void NodalReconstruction::applyDTD ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  R 
) const
inlineprivate

Apply the product of the derivative operator and its adjoint.

Parameters
[in]SNodal field.
[out]RResulting nodal field.
532  {
533  // m_difx/m_dify are sized once in solve(); this routine runs once per CG
534  // iteration, so allocating here would allocate O(iterations) times.
535  applyD(S, m_difx, m_dify);
536  applyDT(m_difx, m_dify, R);
537  }
void applyDT(amrex::FArrayBox const &difx, amrex::FArrayBox const &dify, amrex::FArrayBox &R) const
Apply the adjoint of the derivative operator.
Definition: ERF_NodalReconstruction.H:500
void applyD(amrex::FArrayBox const &S, amrex::FArrayBox &difx, amrex::FArrayBox &dify) const
Compute the first derivatives of the nodal field.
Definition: ERF_NodalReconstruction.H:470
amrex::FArrayBox m_difx
Definition: ERF_NodalReconstruction.H:418
amrex::FArrayBox m_dify
Definition: ERF_NodalReconstruction.H:419

Referenced by applyVariationOperator().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ applyJacobi()

void NodalReconstruction::applyJacobi ( amrex::FArrayBox const &  diag,
amrex::FArrayBox const &  r,
amrex::FArrayBox &  z 
) const
inlineprivate

Apply the Jacobi preconditioner.

Parameters
[in]diagOperator diagonal.
[in]rResidual field.
[out]zPreconditioned residual field.
842  {
843  auto const d_arr = diag.const_array();
844  auto const r_arr = r.const_array();
845  auto const z_arr = z.array();
846  for (int j=m_jlo; j<=m_jhi+1; ++j) {
847  for (int i=m_ilo; i<=m_ihi+1; ++i) {
848  z_arr(i,j,0) = r_arr(i,j,0) / d_arr(i,j,0);
849  }
850  }
851  }

Referenced by conjugateGradient().

Here is the caller graph for this function:

◆ applyL()

void NodalReconstruction::applyL ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  lap 
) const
inlineprivate

Apply the Laplacian operator to a nodal field.

Parameters
[in]SNodal field.
[out]lapResulting Laplacian field.
546  {
547  lap.setVal<amrex::RunOn::Host>(zero);
548 
549  auto const S_arr = S.const_array();
550  auto const lap_arr = lap.array();
551 
552  for (int j = m_jlo + 1; j <= m_jhi; ++j) {
553  for (int i = m_ilo + 1; i <= m_ihi; ++i) {
554  lap_arr(i, j, 0) = m_gxx * ( S_arr(i+1, j , 0)
555  - two * S_arr(i , j , 0)
556  + S_arr(i-1, j , 0) )
557  + m_gyy * ( S_arr(i , j+1, 0)
558  - two * S_arr(i , j , 0)
559  + S_arr(i , j-1, 0) );
560  }
561  }
562  }
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:38

Referenced by applyLTL().

Here is the caller graph for this function:

◆ applyLT()

void NodalReconstruction::applyLT ( amrex::FArrayBox const &  lap,
amrex::FArrayBox &  R 
) const
inlineprivate

Apply the adjoint of the Laplacian operator.

Parameters
[in]lapLaplacian field.
[out]RResulting nodal field.
571  {
572  R.setVal<amrex::RunOn::Host>(zero);
573 
574  auto const q_arr = lap.const_array();
575  auto const R_arr = R.array();
576 
577  for (int j = m_jlo + 1; j <= m_jhi; ++j) {
578  for (int i = m_ilo + 1; i <= m_ihi; ++i) {
579  const amrex::Real val = q_arr(i, j, 0);
580  R_arr(i-1, j , 0) += m_gxx * val;
581  R_arr(i+1, j , 0) += m_gxx * val;
582  R_arr(i , j-1, 0) += m_gyy * val;
583  R_arr(i , j+1, 0) += m_gyy * val;
584  R_arr(i , j , 0) -= two * (m_gxx + m_gyy) * val;
585  }
586  }
587  }

Referenced by applyLTL().

Here is the caller graph for this function:

◆ applyLTL()

void NodalReconstruction::applyLTL ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  R 
) const
inlineprivate

Apply the product of the Laplacian operator and its adjoint.

Parameters
[in]SNodal field.
[out]RResulting nodal field.
596  {
597  if (!m_interior_nodal_box.ok()) {
598  // Fewer than two cells in a direction: there is no interior node to
599  // evaluate the Laplacian at. mu keeps the operator definite.
600  R.setVal<amrex::RunOn::Host>(zero);
601  return;
602  }
603  // m_lap is sized once in solve(); see the note in applyDTD.
604  applyL(S, m_lap);
605  applyLT(m_lap, R);
606  }
void applyLT(amrex::FArrayBox const &lap, amrex::FArrayBox &R) const
Apply the adjoint of the Laplacian operator.
Definition: ERF_NodalReconstruction.H:569
void applyL(amrex::FArrayBox const &S, amrex::FArrayBox &lap) const
Apply the Laplacian operator to a nodal field.
Definition: ERF_NodalReconstruction.H:544
amrex::FArrayBox m_lap
Definition: ERF_NodalReconstruction.H:420

Referenced by applyVariationOperator().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ applyM()

void NodalReconstruction::applyM ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  R 
) const
inlineprivate

R = ( A^T A + lambda V^T V + mu I ) S.

Apply the full system operator M = A^T A + lambda V^T V + mu I.

Parameters
[in]SNodal field.
[out]RResulting nodal field.
638  {
642 
643  auto const R_arr = R.array();
644  auto const v_arr = m_nd_scratch.const_array();
645  auto const S_arr = S.const_array();
646  for (int j=m_jlo; j<=m_jhi+1; ++j) {
647  for (int i=m_ilo; i<=m_ihi+1; ++i) {
648  R_arr(i,j,0) += m_lambda * v_arr(i,j,0) + m_mu * S_arr(i,j,0);
649  }
650  }
651  }
amrex::Real m_lambda
Definition: ERF_NodalReconstruction.H:410
void applyAT(amrex::FArrayBox const &T, amrex::FArrayBox &R) const
Adjoint of applyA: scatter each w-face value to its four nodes.
Definition: ERF_NodalReconstruction.H:447
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) ]
Definition: ERF_NodalReconstruction.H:428
amrex::FArrayBox m_nd_scratch
Definition: ERF_NodalReconstruction.H:415
amrex::FArrayBox m_w_face_scratch
Definition: ERF_NodalReconstruction.H:414
amrex::Real m_mu
Definition: ERF_NodalReconstruction.H:411
void applyVariationOperator(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Apply the selected variation operator (first derivative or Laplacian).
Definition: ERF_NodalReconstruction.H:613

Referenced by conjugateGradient().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ applyVariationOperator()

void NodalReconstruction::applyVariationOperator ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  R 
) const
inlineprivate

Apply the selected variation operator (first derivative or Laplacian).

Parameters
[in]SNodal field.
[out]RResulting nodal field.
615  {
616  switch (m_var_op) {
618  applyDTD(S, R);
619  break;
620 
622  applyLTL(S, R);
623  break;
624 
625  default:
626  amrex::Abort("NodalReconstruction: unsupported variation operator.");
627  }
628  }
void applyLTL(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Apply the product of the Laplacian operator and its adjoint.
Definition: ERF_NodalReconstruction.H:594
VariationOperator m_var_op
Definition: ERF_NodalReconstruction.H:412
void applyDTD(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Apply the product of the derivative operator and its adjoint.
Definition: ERF_NodalReconstruction.H:530

Referenced by applyM().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ computeDiagnostics()

void NodalReconstruction::computeDiagnostics ( amrex::FArrayBox const &  T,
amrex::FArrayBox const &  S,
SolveInfo &  info 
) const
inlineprivate

Compute diagnostic metrics for the reconstructed nodal field.

Parameters
[in]TW-face data.
[in]SReconstructed nodal field.
[in,out]infoStructure to store diagnostics.
714  {
715  auto const S_arr = S.const_array();
716  Real smin = std::numeric_limits<Real>::max();
717  Real smax = -std::numeric_limits<Real>::max();
718  for (int j=m_jlo; j<=m_jhi+1; ++j) {
719  for (int i=m_ilo; i<=m_ihi+1; ++i) {
720  smin = std::min(smin, S_arr(i,j,0));
721  smax = std::max(smax, S_arr(i,j,0));
722  }
723  }
724  info.min_value = smin;
725  info.max_value = smax;
727  }
Real maxAverageError(amrex::FArrayBox const &T, amrex::FArrayBox const &S) const
max_ij | avg4(S) - T |
Definition: ERF_NodalReconstruction.H:350
amrex::Real max_average_error
max |avg4(S) - T|
Definition: ERF_NodalReconstruction.H:44
amrex::Real max_value
max over nodes of S
Definition: ERF_NodalReconstruction.H:47
amrex::Real min_value
min over nodes of S
Definition: ERF_NodalReconstruction.H:46

Referenced by solve().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ computeDiagonal()

void NodalReconstruction::computeDiagonal ( amrex::FArrayBox &  diag) const
inlineprivate

Diagonal of M, assembled by accumulating the square of every stencil coefficient that touches a node. Used as a Jacobi preconditioner.

658  {
659  diag.setVal<amrex::RunOn::Host>(m_mu);
660  auto const d_arr = diag.array();
661 
662  // A^T A: each incident cell contributes (1/4)^2
663  Real const qa = fourth * fourth;
664  for (int j=m_jlo; j<=m_jhi; ++j) {
665  for (int i=m_ilo; i<=m_ihi; ++i) {
666  d_arr(i ,j ,0) += qa;
667  d_arr(i+1,j ,0) += qa;
668  d_arr(i ,j+1,0) += qa;
669  d_arr(i+1,j+1,0) += qa;
670  }
671  }
672 
673  // lambda V^T V
675  Real const qx = m_lambda * m_gx * m_gx;
676  Real const qy = m_lambda * m_gy * m_gy;
677  for (int j=m_jlo; j<=m_jhi+1; ++j) {
678  for (int i=m_ilo; i<=m_ihi; ++i) {
679  d_arr(i ,j,0) += qx;
680  d_arr(i+1,j,0) += qx;
681  }
682  }
683  for (int j=m_jlo; j<=m_jhi; ++j) {
684  for (int i=m_ilo; i<=m_ihi+1; ++i) {
685  d_arr(i,j ,0) += qy;
686  d_arr(i,j+1,0) += qy;
687  }
688  }
689  } else {
690  Real const qx = m_lambda * m_gxx * m_gxx;
691  Real const qy = m_lambda * m_gyy * m_gyy;
692  Real const qc = m_lambda * four * (m_gxx + m_gyy) * (m_gxx + m_gyy);
693  for (int j=m_jlo+1; j<=m_jhi; ++j) {
694  for (int i=m_ilo+1; i<=m_ihi; ++i) {
695  d_arr(i-1,j ,0) += qx;
696  d_arr(i+1,j ,0) += qx;
697  d_arr(i ,j-1,0) += qy;
698  d_arr(i ,j+1,0) += qy;
699  d_arr(i ,j ,0) += qc;
700  }
701  }
702  }
703  }
constexpr amrex::Real four
Definition: ERF_NumericalConstants.H:40
@ qc
Definition: ERF_SatAdj.H:42
@ qa
Definition: ERF_AdvanceWDM6.cpp:276

Referenced by conjugateGradient().

Here is the caller graph for this function:

◆ conjugateGradient()

SolveInfo NodalReconstruction::conjugateGradient ( amrex::FArrayBox const &  rhs,
amrex::FArrayBox &  x,
Real  relative_tolerance,
int  max_iterations 
) const
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.

Local storage for the operator diagonal.

Local storage for the residual.

Local storage for the preconditioned residual.

Local storage for the search direction.

Local storage for the product of the operator and the search direction.

748  {
749  /**
750  * @brief Local storage for the operator diagonal.
751  */
752  amrex::FArrayBox diag(m_nodal_box,1,amrex::The_Managed_Arena());
753  computeDiagonal(diag);
754 
755  /**
756  * @brief Local storage for the residual.
757  */
758  amrex::FArrayBox r (m_nodal_box,1,amrex::The_Managed_Arena());
759  /**
760  * @brief Local storage for the preconditioned residual.
761  */
762  amrex::FArrayBox z (m_nodal_box,1,amrex::The_Managed_Arena());
763  /**
764  * @brief Local storage for the search direction.
765  */
766  amrex::FArrayBox p (m_nodal_box,1,amrex::The_Managed_Arena());
767  /**
768  * @brief Local storage for the product of the operator and the search direction.
769  */
770  amrex::FArrayBox Mp(m_nodal_box,1,amrex::The_Managed_Arena());
771 
772  x.setVal<amrex::RunOn::Host>(zero);
773  r.copy<amrex::RunOn::Host>(rhs,0,0,1); // r = rhs - M*0
774  applyJacobi(diag,r,z);
775  p.copy<amrex::RunOn::Host>(z,0,0,1);
776 
777  Real rz = dot(r,z);
778  Real const initial = std::sqrt(dot(r,r));
779  Real const tol = relative_tolerance * std::max({initial,one});
780 
781  SolveInfo info;
782  info.final_residual = initial;
783  if (initial <= tol) {
784  info.converged = true;
785  return info;
786  }
787 
788  for (int it=0; it<max_iterations; ++it) {
789  applyM(p,Mp);
790  Real const pMp = dot(p,Mp);
791  if (!(pMp > zero) || !std::isfinite(pMp)) {
792  amrex::Abort("NodalReconstruction: CG curvature failure.");
793  }
794 
795  Real const alpha = rz/pMp;
796  {
797  auto const x_arr = x.array();
798  auto const r_arr = r.array();
799  auto const p_arr = p.const_array();
800  auto const Mp_arr = Mp.const_array();
801  for (int j=m_jlo; j<=m_jhi+1; ++j) {
802  for (int i=m_ilo; i<=m_ihi+1; ++i) {
803  x_arr(i,j,0) += alpha * p_arr(i,j,0);
804  r_arr(i,j,0) -= alpha * Mp_arr(i,j,0);
805  }
806  }
807  }
808 
809  info.iterations = it+1;
810  info.final_residual = std::sqrt(dot(r,r));
811  if (info.final_residual <= tol) {
812  info.converged = true;
813  return info;
814  }
815 
816  applyJacobi(diag,r,z);
817  Real const rz_new = dot(r,z);
818  Real const beta = rz_new/rz;
819  {
820  auto const p_arr = p.array();
821  auto const z_arr = z.const_array();
822  for (int j=m_jlo; j<=m_jhi+1; ++j) {
823  for (int i=m_ilo; i<=m_ihi+1; ++i) {
824  p_arr(i,j,0) = z_arr(i,j,0) + beta * p_arr(i,j,0);
825  }
826  }
827  }
828  rz = rz_new;
829  }
830  return info;
831  }
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
void applyJacobi(amrex::FArrayBox const &diag, amrex::FArrayBox const &r, amrex::FArrayBox &z) const
Apply the Jacobi preconditioner.
Definition: ERF_NodalReconstruction.H:839
void computeDiagonal(amrex::FArrayBox &diag) const
Definition: ERF_NodalReconstruction.H:657
void applyM(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
R = ( A^T A + lambda V^T V + mu I ) S.
Definition: ERF_NodalReconstruction.H:636
Real dot(amrex::FArrayBox const &a, amrex::FArrayBox const &b) const
Definition: ERF_NodalReconstruction.H:853
@ beta
Definition: ERF_SLM.H:152
@ alpha
Definition: ERF_SLM.H:151
@ p
Definition: ERF_WSM6.H:280
Convergence and diagnostic information for the reconstruction solve.
Definition: ERF_NodalReconstruction.H:34
amrex::Real final_residual
CG residual of the accepted solve.
Definition: ERF_NodalReconstruction.H:36

Referenced by solve().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ dot()

Real NodalReconstruction::dot ( amrex::FArrayBox const &  a,
amrex::FArrayBox const &  b 
) const
inlineprivate
855  {
856  auto const a_arr = a.const_array();
857  auto const b_arr = b.const_array();
858  Real v = zero;
859  for (int j=m_jlo; j<=m_jhi+1; ++j) {
860  for (int i=m_ilo; i<=m_ihi+1; ++i) {
861  v += a_arr(i,j,0) * b_arr(i,j,0);
862  }
863  }
864  return v;
865  }

Referenced by conjugateGradient().

Here is the caller graph for this function:

◆ makeReference()

amrex::FArrayBox NodalReconstruction::makeReference ( amrex::FArrayBox const &  T) const
inline

Direct interpolation of the w-face data to the nodes: the average of the (up to four) w-faces that touch each node, with edge and corner nodes taking the average of the w-faces that exist. This is the field the solve corrects, and the field it falls back to.

Local storage for the reference nodal field.

207  {
208  /**
209  * @brief Local storage for the reference nodal field.
210  */
211  amrex::FArrayBox R(m_nodal_box,1,amrex::The_Managed_Arena());
212  auto const T_arr = T.const_array();
213  auto const R_arr = R.array();
214 
215  for (int j=m_jlo; j<=m_jhi+1; ++j) {
216  int const jm = std::max(m_jlo, std::min(j-1, m_jhi));
217  int const jp = std::max(m_jlo, std::min(j , m_jhi));
218  for (int i=m_ilo; i<=m_ihi+1; ++i) {
219  int const im = std::max(m_ilo, std::min(i-1, m_ihi));
220  int const ip = std::max(m_ilo, std::min(i , m_ihi));
221  R_arr(i,j,0) = fourth * ( T_arr(im,jm,0) + T_arr(ip,jm,0)
222  + T_arr(im,jp,0) + T_arr(ip,jp,0) );
223  }
224  }
225  return R;
226  }

Referenced by reconstruct_nodal_height_slice().

Here is the caller graph for this function:

◆ maxAbs()

Real NodalReconstruction::maxAbs ( amrex::FArrayBox const &  F) const
inlineprivate
730  {
731  auto const F_arr = F.const_array();
732  Real v = zero;
733  for (int j=m_jlo; j<=m_jhi+1; ++j) {
734  for (int i=m_ilo; i<=m_ihi+1; ++i) {
735  v = std::max(v, std::abs(F_arr(i,j,0)));
736  }
737  }
738  return v;
739  }

Referenced by solve().

Here is the caller graph for this function:

◆ maxAverageError()

Real NodalReconstruction::maxAverageError ( amrex::FArrayBox const &  T,
amrex::FArrayBox const &  S 
) const
inline

max_ij | avg4(S) - T |

352  {
353  auto const T_arr = T.const_array();
354  auto const S_arr = S.const_array();
355  Real err = zero;
356  for (int j=m_jlo; j<=m_jhi; ++j) {
357  for (int i=m_ilo; i<=m_ihi; ++i) {
358  Real const avg = fourth * ( S_arr(i ,j ,0) + S_arr(i+1,j ,0)
359  + S_arr(i ,j+1,0) + S_arr(i+1,j+1,0) );
360  err = std::max(err, std::abs(avg - T_arr(i,j,0)));
361  }
362  }
363  return err;
364  }

Referenced by computeDiagnostics().

Here is the caller graph for this function:

◆ nodalBox()

amrex::Box const& NodalReconstruction::nodalBox ( ) const
inlinenoexcept
196  {
197  return m_nodal_box;
198  }

◆ solve()

std::pair<amrex::FArrayBox,SolveInfo> NodalReconstruction::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 
)
inline

Solve the regularized least-squares problem described above.

Parameters
Tw-face data to be matched
referencethe interpolant to correct (see makeReference)
var_oproughness operator used as the smoothness prior
relative_toleranceCG stopping tolerance
smoothinglambda; weight of the roughness penalty
min_regularizationsmallest mu tried; raised by factors of four until the correction respects its cap
max_iterationsCG iteration cap per attempt

Local storage for the interpolation misfit.

Local storage for the right-hand side of the normal equations.

Local storage for the nodal correction.

Local storage for the reconstructed nodal field.

248  {
249  if (!(smoothing > zero)) {
250  amrex::Abort("NodalReconstruction: smoothing weight must be positive.");
251  }
252  if (!(min_regularization > zero)) {
253  // With mu == 0 the operator can be singular: the roughness penalty
254  // alone does not control every mode (the Laplacian penalty, for
255  // instance, is blind to bilinear and discrete-harmonic fields).
256  amrex::Abort("NodalReconstruction: regularization must be positive.");
257  }
258 
259  m_var_op = var_op;
260  m_lambda = smoothing;
261 
262  m_w_face_scratch.resize(m_w_face_slice_box ,1,amrex::The_Managed_Arena());
263  m_nd_scratch.resize(m_nodal_box,1,amrex::The_Managed_Arena());
264  m_difx.resize(m_x_edge_box,1,amrex::The_Managed_Arena());
265  m_dify.resize(m_y_edge_box,1,amrex::The_Managed_Arena());
266  if (m_interior_nodal_box.ok()) {
267  m_lap.resize(m_interior_nodal_box,1,amrex::The_Managed_Arena());
268  }
269 
270  SolveInfo info;
271 
272  // Misfit of the plain interpolation, and the cap it earns the solve.
273  /**
274  * @brief Local storage for the interpolation misfit.
275  */
276  amrex::FArrayBox misfit(m_w_face_slice_box,1,amrex::The_Managed_Arena());
277  Real misfit_max = zero;
278  Real t_min = std::numeric_limits<Real>::max();
279  Real t_max = -std::numeric_limits<Real>::max();
280  {
281  applyA(reference,misfit);
282  auto const T_arr = T.const_array();
283  auto const m_arr = misfit.array();
284  for (int j=m_jlo; j<=m_jhi; ++j) {
285  for (int i=m_ilo; i<=m_ihi; ++i) {
286  m_arr(i,j,0) = T_arr(i,j,0) - m_arr(i,j,0);
287  misfit_max = std::max(misfit_max, std::abs(m_arr(i,j,0)));
288  t_min = std::min(t_min, T_arr(i,j,0));
289  t_max = std::max(t_max, T_arr(i,j,0));
290  }
291  }
292  }
293  info.interp_average_error = misfit_max;
294  info.deviation_cap = std::min(deviation_factor * misfit_max,
295  relief_factor * (t_max - t_min));
296 
297  // rhs = A^T (T - A S_ref); the correction solves M E = rhs from E = 0.
298  /**
299  * @brief Local storage for the right-hand side of the normal equations.
300  */
301  amrex::FArrayBox rhs(m_nodal_box,1,amrex::The_Managed_Arena());
302  applyAT(misfit,rhs);
303 
304  /**
305  * @brief Local storage for the nodal correction.
306  */
307  amrex::FArrayBox E(m_nodal_box,1,amrex::The_Managed_Arena());
308  /**
309  * @brief Local storage for the reconstructed nodal field.
310  */
311  amrex::FArrayBox S(m_nodal_box,1,amrex::The_Managed_Arena());
312 
313  m_mu = min_regularization;
314  bool accepted = false;
315  for (int attempt=0; attempt<max_attempts; ++attempt) {
316  SolveInfo attempt_info = conjugateGradient(rhs,E,relative_tolerance,max_iterations);
317  info.iterations += attempt_info.iterations;
318  info.final_residual = attempt_info.final_residual;
319  info.converged = attempt_info.converged;
320 
321  info.deviation = maxAbs(E);
322  if (info.deviation <= info.deviation_cap) { accepted = true; break; }
323 
324  m_mu *= mu_growth;
325  ++info.refinements;
326  }
327 
328  if (accepted) {
329  auto const S_arr = S.array();
330  auto const r_arr = reference.const_array();
331  auto const E_arr = E.const_array();
332  for (int j=m_jlo; j<=m_jhi+1; ++j) {
333  for (int i=m_ilo; i<=m_ihi+1; ++i) {
334  S_arr(i,j,0) = r_arr(i,j,0) + E_arr(i,j,0);
335  }
336  }
337  } else {
338  // Should not happen -- ||E|| -> 0 as mu -> infinity -- but if it
339  // does, the interpolant is a safe, bounded answer.
340  S.copy<amrex::RunOn::Host>(reference,0,0,1);
341  info.deviation = zero;
342  }
343  info.regularization = m_mu;
344 
345  computeDiagnostics(T,S,info);
346  return {std::move(S),info};
347  }
SolveInfo conjugateGradient(amrex::FArrayBox const &rhs, amrex::FArrayBox &x, Real relative_tolerance, int max_iterations) const
Definition: ERF_NodalReconstruction.H:744
static constexpr amrex::Real relief_factor
... nor than this fraction of the relief of the data.
Definition: ERF_NodalReconstruction.H:391
Real maxAbs(amrex::FArrayBox const &F) const
Definition: ERF_NodalReconstruction.H:729
static constexpr amrex::Real mu_growth
Definition: ERF_NodalReconstruction.H:394
static constexpr amrex::Real deviation_factor
Definition: ERF_NodalReconstruction.H:389
void computeDiagnostics(amrex::FArrayBox const &T, amrex::FArrayBox const &S, SolveInfo &info) const
Compute diagnostic metrics for the reconstructed nodal field.
Definition: ERF_NodalReconstruction.H:711
static constexpr int max_attempts
Definition: ERF_NodalReconstruction.H:395
int iterations
total CG iterations, summed over attempts
Definition: ERF_NodalReconstruction.H:35
bool converged
Definition: ERF_NodalReconstruction.H:37
int refinements
times the regularization had to be raised
Definition: ERF_NodalReconstruction.H:38
amrex::Real deviation
max |S - S_ref|
Definition: ERF_NodalReconstruction.H:42
amrex::Real deviation_cap
bound imposed on the above
Definition: ERF_NodalReconstruction.H:43
amrex::Real interp_average_error
max |avg4(S_ref) - T|, for comparison
Definition: ERF_NodalReconstruction.H:45
amrex::Real regularization
accepted value of mu
Definition: ERF_NodalReconstruction.H:39

Referenced by reconstruct_nodal_height_slice().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ totalSquaredVariation()

Real NodalReconstruction::totalSquaredVariation ( amrex::FArrayBox const &  S) const
inline
367  {
368  auto const S_arr = S.const_array();
369  Real value = zero;
370 
371  for (int j=m_jlo; j<=m_jhi+1; ++j) {
372  for (int i=m_ilo; i<=m_ihi; ++i) {
373  Real const d = S_arr(i+1,j,0) - S_arr(i,j,0);
374  value += d*d;
375  }
376  }
377  for (int j=m_jlo; j<=m_jhi; ++j) {
378  for (int i=m_ilo; i<=m_ihi+1; ++i) {
379  Real const d = S_arr(i,j+1,0) - S_arr(i,j,0);
380  value += d*d;
381  }
382  }
383  return value;
384  }

Member Data Documentation

◆ deviation_factor

constexpr amrex::Real NodalReconstruction::deviation_factor = amrex::Real(2.0)
staticconstexprprivate

The correction may not move a node further from the direct interpolation than this multiple of the interpolation misfit ...

Referenced by solve().

◆ m_difx

amrex::FArrayBox NodalReconstruction::m_difx
mutableprivate

Scratch for the roughness operators, which are applied once per CG iteration; sized in solve() so the iteration itself never allocates.

Referenced by applyDTD(), and solve().

◆ m_dify

amrex::FArrayBox NodalReconstruction::m_dify
mutableprivate

Referenced by applyDTD(), and solve().

◆ m_gx

amrex::Real NodalReconstruction::m_gx = one
private

◆ m_gxx

amrex::Real NodalReconstruction::m_gxx = one
private

◆ m_gy

amrex::Real NodalReconstruction::m_gy = one
private

◆ m_gyy

amrex::Real NodalReconstruction::m_gyy = one
private

◆ m_ihi

◆ m_ilo

◆ m_interior_nodal_box

amrex::Box NodalReconstruction::m_interior_nodal_box
private

◆ m_jhi

◆ m_jlo

◆ m_lambda

amrex::Real NodalReconstruction::m_lambda = zero
private

Referenced by applyM(), computeDiagonal(), and solve().

◆ m_lap

amrex::FArrayBox NodalReconstruction::m_lap
mutableprivate

Referenced by applyLTL(), and solve().

◆ m_mu

amrex::Real NodalReconstruction::m_mu = zero
private

Referenced by applyM(), computeDiagonal(), and solve().

◆ m_nd_scratch

amrex::FArrayBox NodalReconstruction::m_nd_scratch
mutableprivate

Referenced by applyM(), and solve().

◆ m_nodal_box

amrex::Box NodalReconstruction::m_nodal_box
private

◆ m_var_op

VariationOperator NodalReconstruction::m_var_op = VariationOperator::FirstDeriv
private

◆ m_w_face_scratch

amrex::FArrayBox NodalReconstruction::m_w_face_scratch
mutableprivate

Referenced by applyM(), and solve().

◆ m_w_face_slice_box

amrex::Box NodalReconstruction::m_w_face_slice_box
private

Referenced by solve().

◆ m_x_edge_box

amrex::Box NodalReconstruction::m_x_edge_box
private

Referenced by NodalReconstruction(), and solve().

◆ m_y_edge_box

amrex::Box NodalReconstruction::m_y_edge_box
private

Referenced by NodalReconstruction(), and solve().

◆ max_attempts

constexpr int NodalReconstruction::max_attempts = 24
staticconstexprprivate

Referenced by solve().

◆ mu_growth

constexpr amrex::Real NodalReconstruction::mu_growth = amrex::Real(4.0)
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().

◆ relief_factor

constexpr amrex::Real NodalReconstruction::relief_factor = amrex::Real(0.25)
staticconstexprprivate

... nor than this fraction of the relief of the data.

Referenced by solve().


The documentation for this class was generated from the following file: