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 &face_box, amrex::Geometry const &geom)
 
amrex::Box const & nodalBox () const noexcept
 
amrex::FArrayBox makeReference (amrex::FArrayBox const &T) const
 
std::pair< amrex::FArrayBox, SolveInfosolve (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
 

Detailed Description

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.


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 &  face_box,
amrex::Geometry const &  geom 
)
inlineexplicit
126  : m_face_box(face_box),
127  m_ilo(face_box.smallEnd(0)),
128  m_ihi(face_box.bigEnd(0)),
129  m_jlo(face_box.smallEnd(1)),
130  m_jhi(face_box.bigEnd(1))
131  {
132  if (face_box.length(2) != 1) {
133  throw std::invalid_argument(
134  "face_box must contain exactly one z cell.");
135  }
136  if (face_box.smallEnd(2) != 0) {
137  throw std::invalid_argument(
138  "face_box must be a slab at k = 0; all fabs are indexed at k = 0.");
139  }
140 
141  m_nodal_box = amrex::surroundingNodes(face_box);
142  m_nodal_box.setSmall(2, 0);
143  m_nodal_box.setBig(2, 0);
144 
145  Real const dx = geom.CellSizeArray()[0];
146  Real const dy = geom.CellSizeArray()[1];
147  if (!(dx > zero) || !(dy > zero)) {
148  throw std::invalid_argument("Cell sizes must be positive.");
149  }
150 
151  // Non-dimensional, O(1) stencil weights that respect grid anisotropy.
152  // They are unity on an isotropic grid, so the smoothing weight has the
153  // same meaning regardless of the mesh spacing.
154  Real const h = std::min(dx,dy);
155  m_gx = h / dx;
156  m_gy = h / dy;
157  m_gxx = m_gx * m_gx;
158  m_gyy = m_gy * m_gy;
159  }
constexpr amrex::Real zero
Definition: ERF_Constants.H:8
const Real dy
Definition: ERF_InitCustomPert_ABL.H:24
const Real dx
Definition: ERF_InitCustomPert_ABL.H:23
amrex::Real Real
Definition: ERF_ShocInterface.H:19
amrex::Real m_gyy
Definition: ERF_NodalReconstruction.H:351
int m_ihi
Definition: ERF_NodalReconstruction.H:345
int m_ilo
Definition: ERF_NodalReconstruction.H:344
amrex::Real m_gxx
Definition: ERF_NodalReconstruction.H:350
amrex::Box m_nodal_box
Definition: ERF_NodalReconstruction.H:343
int m_jlo
Definition: ERF_NodalReconstruction.H:346
amrex::Real m_gx
Definition: ERF_NodalReconstruction.H:348
amrex::Real m_gy
Definition: ERF_NodalReconstruction.H:349
amrex::Box m_face_box
Definition: ERF_NodalReconstruction.H:342
int m_jhi
Definition: ERF_NodalReconstruction.H:347

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) ]

362  {
363  auto const S_arr = S.const_array();
364  auto const T_arr = T.array();
365  for (int j=m_jlo; j<=m_jhi; ++j) {
366  for (int i=m_ilo; i<=m_ihi; ++i) {
367  T_arr(i,j,0) = fourth * ( S_arr(i ,j ,0) + S_arr(i+1,j ,0)
368  + S_arr(i ,j+1,0) + S_arr(i+1,j+1,0) );
369  }
370  }
371  }
constexpr amrex::Real fourth
Definition: ERF_Constants.H:14
Real T
Definition: ERF_InitCustomPert_Bubble.H:106

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 cell value to its four nodes.

376  {
377  R.setVal<amrex::RunOn::Host>(zero);
378  auto const T_arr = T.const_array();
379  auto const R_arr = R.array();
380  for (int j=m_jlo; j<=m_jhi; ++j) {
381  for (int i=m_ilo; i<=m_ihi; ++i) {
382  Real const q = fourth * T_arr(i,j,0);
383  R_arr(i ,j ,0) += q;
384  R_arr(i+1,j ,0) += q;
385  R_arr(i ,j+1,0) += q;
386  R_arr(i+1,j+1,0) += q;
387  }
388  }
389  }
@ R
Definition: ERF_IndexDefines.H:127
@ q
Definition: ERF_WSM6.H:184

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
394  {
395  difx.setVal<amrex::RunOn::Host>(zero);
396  dify.setVal<amrex::RunOn::Host>(zero);
397 
398  auto const S_arr = S.const_array();
399  auto const difx_arr = difx.array();
400  auto const dify_arr = dify.array();
401 
402  for (int j = m_jlo; j <= m_jhi + 1; ++j) {
403  for (int i = m_ilo; i <= m_ihi; ++i) {
404  difx_arr(i, j, 0) = m_gx * ( S_arr(i+1, j, 0) - S_arr(i, j, 0) );
405  }
406  }
407 
408  for (int j = m_jlo; j <= m_jhi; ++j) {
409  for (int i = m_ilo; i <= m_ihi + 1; ++i) {
410  dify_arr(i, j, 0) = m_gy * ( S_arr(i, j+1, 0) - S_arr(i, j, 0) );
411  }
412  }
413  }

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
418  {
419  R.setVal<amrex::RunOn::Host>(zero);
420 
421  auto const qx_arr = difx.const_array();
422  auto const qy_arr = dify.const_array();
423  auto const R_arr = R.array();
424 
425  for (int j = m_jlo; j <= m_jhi + 1; ++j) {
426  for (int i = m_ilo; i <= m_ihi; ++i) {
427  R_arr(i , j, 0) -= m_gx * qx_arr(i, j, 0);
428  R_arr(i+1, j, 0) += m_gx * qx_arr(i, j, 0);
429  }
430  }
431 
432  for (int j = m_jlo; j <= m_jhi; ++j) {
433  for (int i = m_ilo; i <= m_ihi + 1; ++i) {
434  R_arr(i, j , 0) -= m_gy * qy_arr(i, j, 0);
435  R_arr(i, j+1, 0) += m_gy * qy_arr(i, j, 0);
436  }
437  }
438  }

Referenced by applyDTD().

Here is the caller graph for this function:

◆ applyDTD()

void NodalReconstruction::applyDTD ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  R 
) const
inlineprivate
442  {
443  amrex::Box x_edge_box = m_nodal_box;
444  amrex::Box y_edge_box = m_nodal_box;
445  x_edge_box.growHi(0,-1);
446  y_edge_box.growHi(1,-1);
447  amrex::FArrayBox difx(x_edge_box,1,amrex::The_Managed_Arena());
448  amrex::FArrayBox dify(y_edge_box,1,amrex::The_Managed_Arena());
449  applyD(S, difx, dify);
450  applyDT(difx, dify, R);
451  }
void applyDT(amrex::FArrayBox const &difx, amrex::FArrayBox const &dify, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:415
void applyD(amrex::FArrayBox const &S, amrex::FArrayBox &difx, amrex::FArrayBox &dify) const
Definition: ERF_NodalReconstruction.H:391

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
707  {
708  auto const d_arr = diag.const_array();
709  auto const r_arr = r.const_array();
710  auto const z_arr = z.array();
711  for (int j=m_jlo; j<=m_jhi+1; ++j) {
712  for (int i=m_ilo; i<=m_ihi+1; ++i) {
713  z_arr(i,j,0) = r_arr(i,j,0) / d_arr(i,j,0);
714  }
715  }
716  }

Referenced by conjugateGradient().

Here is the caller graph for this function:

◆ applyL()

void NodalReconstruction::applyL ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  lap 
) const
inlineprivate
455  {
456  lap.setVal<amrex::RunOn::Host>(zero);
457 
458  auto const S_arr = S.const_array();
459  auto const lap_arr = lap.array();
460 
461  for (int j = m_jlo + 1; j <= m_jhi; ++j) {
462  for (int i = m_ilo + 1; i <= m_ihi; ++i) {
463  lap_arr(i, j, 0) = m_gxx * ( S_arr(i+1, j , 0)
464  - two * S_arr(i , j , 0)
465  + S_arr(i-1, j , 0) )
466  + m_gyy * ( S_arr(i , j+1, 0)
467  - two * S_arr(i , j , 0)
468  + S_arr(i , j-1, 0) );
469  }
470  }
471  }
constexpr amrex::Real two
Definition: ERF_Constants.H:10

Referenced by applyLTL().

Here is the caller graph for this function:

◆ applyLT()

void NodalReconstruction::applyLT ( amrex::FArrayBox const &  lap,
amrex::FArrayBox &  R 
) const
inlineprivate
475  {
476  R.setVal<amrex::RunOn::Host>(zero);
477 
478  auto const q_arr = lap.const_array();
479  auto const R_arr = R.array();
480 
481  for (int j = m_jlo + 1; j <= m_jhi; ++j) {
482  for (int i = m_ilo + 1; i <= m_ihi; ++i) {
483  const amrex::Real val = q_arr(i, j, 0);
484  R_arr(i-1, j , 0) += m_gxx * val;
485  R_arr(i+1, j , 0) += m_gxx * val;
486  R_arr(i , j-1, 0) += m_gyy * val;
487  R_arr(i , j+1, 0) += m_gyy * val;
488  R_arr(i , j , 0) -= two * (m_gxx + m_gyy) * val;
489  }
490  }
491  }

Referenced by applyLTL().

Here is the caller graph for this function:

◆ applyLTL()

void NodalReconstruction::applyLTL ( amrex::FArrayBox const &  S,
amrex::FArrayBox &  R 
) const
inlineprivate
495  {
496  amrex::Box interior_nodal_box = m_nodal_box;
497  interior_nodal_box.grow(0,-1);
498  interior_nodal_box.grow(1,-1);
499  if (!interior_nodal_box.ok()) {
500  // Fewer than two cells in a direction: there is no interior node to
501  // evaluate the Laplacian at. mu keeps the operator definite.
502  R.setVal<amrex::RunOn::Host>(zero);
503  return;
504  }
505  amrex::FArrayBox lap(interior_nodal_box,1,amrex::The_Managed_Arena());
506  applyL(S, lap);
507  applyLT(lap, R);
508  }
void applyLT(amrex::FArrayBox const &lap, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:473
void applyL(amrex::FArrayBox const &S, amrex::FArrayBox &lap) const
Definition: ERF_NodalReconstruction.H:453

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.

530  {
531  applyA(S,m_cc_scratch);
534 
535  auto const R_arr = R.array();
536  auto const v_arr = m_nd_scratch.const_array();
537  auto const S_arr = S.const_array();
538  for (int j=m_jlo; j<=m_jhi+1; ++j) {
539  for (int i=m_ilo; i<=m_ihi+1; ++i) {
540  R_arr(i,j,0) += m_lambda * v_arr(i,j,0) + m_mu * S_arr(i,j,0);
541  }
542  }
543  }
amrex::Real m_lambda
Definition: ERF_NodalReconstruction.H:352
void applyAT(amrex::FArrayBox const &T, amrex::FArrayBox &R) const
Adjoint of applyA: scatter each cell value to its four nodes.
Definition: ERF_NodalReconstruction.H:374
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:360
amrex::FArrayBox m_nd_scratch
Definition: ERF_NodalReconstruction.H:357
amrex::FArrayBox m_cc_scratch
Definition: ERF_NodalReconstruction.H:356
amrex::Real m_mu
Definition: ERF_NodalReconstruction.H:353
void applyVariationOperator(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:510

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
512  {
513  switch (m_var_op) {
515  applyDTD(S, R);
516  break;
517 
519  applyLTL(S, R);
520  break;
521 
522  default:
523  throw std::runtime_error("Unsupported variation operator.");
524  }
525  }
void applyLTL(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:493
VariationOperator m_var_op
Definition: ERF_NodalReconstruction.H:354
void applyDTD(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:440

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
600  {
601  auto const S_arr = S.const_array();
602  Real smin = std::numeric_limits<Real>::max();
603  Real smax = -std::numeric_limits<Real>::max();
604  for (int j=m_jlo; j<=m_jhi+1; ++j) {
605  for (int i=m_ilo; i<=m_ihi+1; ++i) {
606  smin = std::min(smin, S_arr(i,j,0));
607  smax = std::max(smax, S_arr(i,j,0));
608  }
609  }
610  info.min_value = smin;
611  info.max_value = smax;
613  }
Real maxAverageError(amrex::FArrayBox const &T, amrex::FArrayBox const &S) const
max_ij | avg4(S) - T |
Definition: ERF_NodalReconstruction.H:295
amrex::Real max_average_error
max |avg4(S) - T|
Definition: ERF_NodalReconstruction.H:34
amrex::Real max_value
max over nodes of S
Definition: ERF_NodalReconstruction.H:37
amrex::Real min_value
min over nodes of S
Definition: ERF_NodalReconstruction.H:36

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.

550  {
551  diag.setVal<amrex::RunOn::Host>(m_mu);
552  auto const d_arr = diag.array();
553 
554  // A^T A: each incident cell contributes (1/4)^2
555  Real const qa = fourth * fourth;
556  for (int j=m_jlo; j<=m_jhi; ++j) {
557  for (int i=m_ilo; i<=m_ihi; ++i) {
558  d_arr(i ,j ,0) += qa;
559  d_arr(i+1,j ,0) += qa;
560  d_arr(i ,j+1,0) += qa;
561  d_arr(i+1,j+1,0) += qa;
562  }
563  }
564 
565  // lambda V^T V
567  Real const qx = m_lambda * m_gx * m_gx;
568  Real const qy = m_lambda * m_gy * m_gy;
569  for (int j=m_jlo; j<=m_jhi+1; ++j) {
570  for (int i=m_ilo; i<=m_ihi; ++i) {
571  d_arr(i ,j,0) += qx;
572  d_arr(i+1,j,0) += qx;
573  }
574  }
575  for (int j=m_jlo; j<=m_jhi; ++j) {
576  for (int i=m_ilo; i<=m_ihi+1; ++i) {
577  d_arr(i,j ,0) += qy;
578  d_arr(i,j+1,0) += qy;
579  }
580  }
581  } else {
582  Real const qx = m_lambda * m_gxx * m_gxx;
583  Real const qy = m_lambda * m_gyy * m_gyy;
584  Real const qc = m_lambda * four * (m_gxx + m_gyy) * (m_gxx + m_gyy);
585  for (int j=m_jlo+1; j<=m_jhi; ++j) {
586  for (int i=m_ilo+1; i<=m_ihi; ++i) {
587  d_arr(i-1,j ,0) += qx;
588  d_arr(i+1,j ,0) += qx;
589  d_arr(i ,j-1,0) += qy;
590  d_arr(i ,j+1,0) += qy;
591  d_arr(i ,j ,0) += qc;
592  }
593  }
594  }
595  }
constexpr amrex::Real four
Definition: ERF_Constants.H:12
@ qc
Definition: ERF_SatAdj.H:40
@ qa
Definition: ERF_AdvanceWSM6.cpp:136

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.

634  {
635  amrex::FArrayBox diag(m_nodal_box,1,amrex::The_Managed_Arena());
636  computeDiagonal(diag);
637 
638  amrex::FArrayBox r (m_nodal_box,1,amrex::The_Managed_Arena());
639  amrex::FArrayBox z (m_nodal_box,1,amrex::The_Managed_Arena());
640  amrex::FArrayBox p (m_nodal_box,1,amrex::The_Managed_Arena());
641  amrex::FArrayBox Mp(m_nodal_box,1,amrex::The_Managed_Arena());
642 
643  x.setVal<amrex::RunOn::Host>(zero);
644  r.copy<amrex::RunOn::Host>(rhs,0,0,1); // r = rhs - M*0
645  applyJacobi(diag,r,z);
646  p.copy<amrex::RunOn::Host>(z,0,0,1);
647 
648  Real rz = dot(r,z);
649  Real const initial = std::sqrt(dot(r,r));
650  Real const tol = relative_tolerance * std::max({initial,one});
651 
652  SolveInfo info;
653  info.final_residual = initial;
654  if (initial <= tol) {
655  info.converged = true;
656  return info;
657  }
658 
659  for (int it=0; it<max_iterations; ++it) {
660  applyM(p,Mp);
661  Real const pMp = dot(p,Mp);
662  if (!(pMp > zero) || !std::isfinite(pMp)) {
663  throw std::runtime_error("CG curvature failure.");
664  }
665 
666  Real const alpha = rz/pMp;
667  {
668  auto const x_arr = x.array();
669  auto const r_arr = r.array();
670  auto const p_arr = p.const_array();
671  auto const Mp_arr = Mp.const_array();
672  for (int j=m_jlo; j<=m_jhi+1; ++j) {
673  for (int i=m_ilo; i<=m_ihi+1; ++i) {
674  x_arr(i,j,0) += alpha * p_arr(i,j,0);
675  r_arr(i,j,0) -= alpha * Mp_arr(i,j,0);
676  }
677  }
678  }
679 
680  info.iterations = it+1;
681  info.final_residual = std::sqrt(dot(r,r));
682  if (info.final_residual <= tol) {
683  info.converged = true;
684  return info;
685  }
686 
687  applyJacobi(diag,r,z);
688  Real const rz_new = dot(r,z);
689  Real const beta = rz_new/rz;
690  {
691  auto const p_arr = p.array();
692  auto const z_arr = z.const_array();
693  for (int j=m_jlo; j<=m_jhi+1; ++j) {
694  for (int i=m_ilo; i<=m_ihi+1; ++i) {
695  p_arr(i,j,0) = z_arr(i,j,0) + beta * p_arr(i,j,0);
696  }
697  }
698  }
699  rz = rz_new;
700  }
701  return info;
702  }
constexpr amrex::Real one
Definition: ERF_Constants.H:9
amrex::Real beta
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:10
void applyJacobi(amrex::FArrayBox const &diag, amrex::FArrayBox const &r, amrex::FArrayBox &z) const
Definition: ERF_NodalReconstruction.H:704
void computeDiagonal(amrex::FArrayBox &diag) const
Definition: ERF_NodalReconstruction.H:549
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:528
Real dot(amrex::FArrayBox const &a, amrex::FArrayBox const &b) const
Definition: ERF_NodalReconstruction.H:718
@ p
Definition: ERF_WSM6.H:191
real(kind=kind_phys), parameter, public alpha
Definition: ERF_module_mp_wsm6.F90:44
Definition: ERF_NodalReconstruction.H:24
amrex::Real final_residual
CG residual of the accepted solve.
Definition: ERF_NodalReconstruction.H:26

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
720  {
721  auto const a_arr = a.const_array();
722  auto const b_arr = b.const_array();
723  Real v = zero;
724  for (int j=m_jlo; j<=m_jhi+1; ++j) {
725  for (int i=m_ilo; i<=m_ihi+1; ++i) {
726  v += a_arr(i,j,0) * b_arr(i,j,0);
727  }
728  }
729  return v;
730  }

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 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.

172  {
173  amrex::FArrayBox R(m_nodal_box,1,amrex::The_Managed_Arena());
174  auto const T_arr = T.const_array();
175  auto const R_arr = R.array();
176 
177  for (int j=m_jlo; j<=m_jhi+1; ++j) {
178  int const jm = std::max(m_jlo, std::min(j-1, m_jhi));
179  int const jp = std::max(m_jlo, std::min(j , m_jhi));
180  for (int i=m_ilo; i<=m_ihi+1; ++i) {
181  int const im = std::max(m_ilo, std::min(i-1, m_ihi));
182  int const ip = std::max(m_ilo, std::min(i , m_ihi));
183  R_arr(i,j,0) = fourth * ( T_arr(im,jm,0) + T_arr(ip,jm,0)
184  + T_arr(im,jp,0) + T_arr(ip,jp,0) );
185  }
186  }
187  return R;
188  }

◆ maxAbs()

Real NodalReconstruction::maxAbs ( amrex::FArrayBox const &  F) const
inlineprivate
616  {
617  auto const F_arr = F.const_array();
618  Real v = zero;
619  for (int j=m_jlo; j<=m_jhi+1; ++j) {
620  for (int i=m_ilo; i<=m_ihi+1; ++i) {
621  v = std::max(v, std::abs(F_arr(i,j,0)));
622  }
623  }
624  return v;
625  }

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 |

297  {
298  auto const T_arr = T.const_array();
299  auto const S_arr = S.const_array();
300  Real err = zero;
301  for (int j=m_jlo; j<=m_jhi; ++j) {
302  for (int i=m_ilo; i<=m_ihi; ++i) {
303  Real const avg = fourth * ( S_arr(i ,j ,0) + S_arr(i+1,j ,0)
304  + S_arr(i ,j+1,0) + S_arr(i+1,j+1,0) );
305  err = std::max(err, std::abs(avg - T_arr(i,j,0)));
306  }
307  }
308  return err;
309  }

Referenced by computeDiagnostics().

Here is the caller graph for this function:

◆ nodalBox()

amrex::Box const& NodalReconstruction::nodalBox ( ) const
inlinenoexcept
161  {
162  return m_nodal_box;
163  }

◆ 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
Tcell-centered 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
210  {
211  if (!(smoothing > zero)) {
212  throw std::invalid_argument("Smoothing weight must be positive.");
213  }
214  if (!(min_regularization > zero)) {
215  // With mu == 0 the operator can be singular: the roughness penalty
216  // alone does not control every mode (the Laplacian penalty, for
217  // instance, is blind to bilinear and discrete-harmonic fields).
218  throw std::invalid_argument("Regularization must be positive.");
219  }
220 
221  m_var_op = var_op;
222  m_lambda = smoothing;
223 
224  m_cc_scratch.resize(m_face_box ,1,amrex::The_Managed_Arena());
225  m_nd_scratch.resize(m_nodal_box,1,amrex::The_Managed_Arena());
226 
227  SolveInfo info;
228 
229  // Misfit of the plain interpolation, and the cap it earns the solve.
230  amrex::FArrayBox misfit(m_face_box,1,amrex::The_Managed_Arena());
231  Real misfit_max = zero;
232  Real t_min = std::numeric_limits<Real>::max();
233  Real t_max = -std::numeric_limits<Real>::max();
234  {
235  applyA(reference,misfit);
236  auto const T_arr = T.const_array();
237  auto const m_arr = misfit.array();
238  for (int j=m_jlo; j<=m_jhi; ++j) {
239  for (int i=m_ilo; i<=m_ihi; ++i) {
240  m_arr(i,j,0) = T_arr(i,j,0) - m_arr(i,j,0);
241  misfit_max = std::max(misfit_max, std::abs(m_arr(i,j,0)));
242  t_min = std::min(t_min, T_arr(i,j,0));
243  t_max = std::max(t_max, T_arr(i,j,0));
244  }
245  }
246  }
247  info.interp_average_error = misfit_max;
248  info.deviation_cap = std::min(deviation_factor * misfit_max,
249  relief_factor * (t_max - t_min));
250 
251  // rhs = A^T (T - A S_ref); the correction solves M E = rhs from E = 0.
252  amrex::FArrayBox rhs(m_nodal_box,1,amrex::The_Managed_Arena());
253  applyAT(misfit,rhs);
254 
255  amrex::FArrayBox E(m_nodal_box,1,amrex::The_Managed_Arena());
256  amrex::FArrayBox S(m_nodal_box,1,amrex::The_Managed_Arena());
257 
258  m_mu = min_regularization;
259  bool accepted = false;
260  for (int attempt=0; attempt<max_attempts; ++attempt) {
261  SolveInfo attempt_info = conjugateGradient(rhs,E,relative_tolerance,max_iterations);
262  info.iterations += attempt_info.iterations;
263  info.final_residual = attempt_info.final_residual;
264  info.converged = attempt_info.converged;
265 
266  info.deviation = maxAbs(E);
267  if (info.deviation <= info.deviation_cap) { accepted = true; break; }
268 
269  m_mu *= mu_growth;
270  ++info.refinements;
271  }
272 
273  if (accepted) {
274  auto const S_arr = S.array();
275  auto const r_arr = reference.const_array();
276  auto const E_arr = E.const_array();
277  for (int j=m_jlo; j<=m_jhi+1; ++j) {
278  for (int i=m_ilo; i<=m_ihi+1; ++i) {
279  S_arr(i,j,0) = r_arr(i,j,0) + E_arr(i,j,0);
280  }
281  }
282  } else {
283  // Should not happen -- ||E|| -> 0 as mu -> infinity -- but if it
284  // does, the interpolant is a safe, bounded answer.
285  S.copy<amrex::RunOn::Host>(reference,0,0,1);
286  info.deviation = zero;
287  }
288  info.regularization = m_mu;
289 
290  computeDiagnostics(T,S,info);
291  return {std::move(S),info};
292  }
SolveInfo conjugateGradient(amrex::FArrayBox const &rhs, amrex::FArrayBox &x, Real relative_tolerance, int max_iterations) const
Definition: ERF_NodalReconstruction.H:630
static constexpr amrex::Real relief_factor
... nor than this fraction of the relief of the data.
Definition: ERF_NodalReconstruction.H:336
Real maxAbs(amrex::FArrayBox const &F) const
Definition: ERF_NodalReconstruction.H:615
static constexpr amrex::Real mu_growth
Definition: ERF_NodalReconstruction.H:339
static constexpr amrex::Real deviation_factor
Definition: ERF_NodalReconstruction.H:334
void computeDiagnostics(amrex::FArrayBox const &T, amrex::FArrayBox const &S, SolveInfo &info) const
Definition: ERF_NodalReconstruction.H:597
static constexpr int max_attempts
Definition: ERF_NodalReconstruction.H:340
int iterations
total CG iterations, summed over attempts
Definition: ERF_NodalReconstruction.H:25
bool converged
Definition: ERF_NodalReconstruction.H:27
int refinements
times the regularization had to be raised
Definition: ERF_NodalReconstruction.H:28
amrex::Real deviation
max |S - S_ref|
Definition: ERF_NodalReconstruction.H:32
amrex::Real deviation_cap
bound imposed on the above
Definition: ERF_NodalReconstruction.H:33
amrex::Real interp_average_error
max |avg4(S_ref) - T|, for comparison
Definition: ERF_NodalReconstruction.H:35
amrex::Real regularization
accepted value of mu
Definition: ERF_NodalReconstruction.H:29
Here is the call graph for this function:

◆ totalSquaredVariation()

Real NodalReconstruction::totalSquaredVariation ( amrex::FArrayBox const &  S) const
inline
312  {
313  auto const S_arr = S.const_array();
314  Real value = zero;
315 
316  for (int j=m_jlo; j<=m_jhi+1; ++j) {
317  for (int i=m_ilo; i<=m_ihi; ++i) {
318  Real const d = S_arr(i+1,j,0) - S_arr(i,j,0);
319  value += d*d;
320  }
321  }
322  for (int j=m_jlo; j<=m_jhi; ++j) {
323  for (int i=m_ilo; i<=m_ihi+1; ++i) {
324  Real const d = S_arr(i,j+1,0) - S_arr(i,j,0);
325  value += d*d;
326  }
327  }
328  return value;
329  }
Real value
Definition: ERF_HurricaneDiagnostics.cpp:30

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_cc_scratch

amrex::FArrayBox NodalReconstruction::m_cc_scratch
mutableprivate

Referenced by applyM(), and solve().

◆ m_face_box

amrex::Box NodalReconstruction::m_face_box
private

Referenced by 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_jhi

◆ m_jlo

◆ m_lambda

amrex::Real NodalReconstruction::m_lambda = zero
private

Referenced by applyM(), computeDiagonal(), 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

◆ 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: