ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_NodalReconstruction.H
Go to the documentation of this file.
1 #ifndef ERF_NODAL_RECONSTRUCTION_H_
2 #define ERF_NODAL_RECONSTRUCTION_H_
3 
4 #include <algorithm>
5 #include <cmath>
6 #include <limits>
7 #include <stdexcept>
8 #include <utility>
9 
10 #include "AMReX_Array4.H"
11 #include "AMReX_Box.H"
12 #include "AMReX_FArrayBox.H"
13 #include "AMReX_Geometry.H"
14 #include "AMReX_REAL.H"
15 
16 #include "ERF_Constants.H"
17 
19 {
20  FirstDeriv,
21  Laplacian
22 };
23 
24 struct SolveInfo {
25  int iterations = 0; //!< total CG iterations, summed over attempts
26  amrex::Real final_residual = zero; //!< CG residual of the accepted solve
27  bool converged = false;
28  int refinements = 0; //!< times the regularization had to be raised
29  amrex::Real regularization = zero; //!< accepted value of mu
30 
31  // Diagnostics on the reconstructed field
32  amrex::Real deviation = zero; //!< max |S - S_ref|
33  amrex::Real deviation_cap = zero; //!< bound imposed on the above
34  amrex::Real max_average_error = zero; //!< max |avg4(S) - T|
35  amrex::Real interp_average_error = zero; //!< max |avg4(S_ref) - T|, for comparison
36  amrex::Real min_value = zero; //!< min over nodes of S
37  amrex::Real max_value = zero; //!< max over nodes of S
38 };
39 
40 /**
41  * Reconstruct a nodal field S from data T that lives at cell centers in (x,y),
42  * such that the four-node average of S matches T as closely as a *bounded*,
43  * smooth nodal field can.
44  *
45  * ---------------------------------------------------------------------------
46  * Why we do not simply invert the averaging operator
47  * ---------------------------------------------------------------------------
48  * The four-node averaging operator A,
49  *
50  * (A S)(i,j) = 1/4 [ S(i,j) + S(i+1,j) + S(i,j+1) + S(i+1,j+1) ] ,
51  *
52  * has symbol cos(kx/2) cos(ky/2), which vanishes at the grid Nyquist mode.
53  * Inverting A exactly -- e.g. with the back substitution
54  *
55  * S(i+1,j+1) = 4 T(i,j) - S(i,j) - S(i+1,j) - S(i,j+1)
56  *
57  * -- therefore amplifies grid-scale content of T without bound. The Green's
58  * function of that recursion is 4 / [(1+x)(1+y)], a doubly alternating
59  * cumulative sum, so a checkerboard component of T of amplitude a produces a
60  * nodal response of amplitude a*i*j. On a 128^2 grid an O(10 m) grid-scale
61  * roughness in the WRF terrain thus yields nodal heights that are kilometers
62  * away from the WRF terrain, including physically impossible negative
63  * elevations (ERF issue #3709). Adding a free homogeneous solution cannot
64  * repair this: the null space of A is spanned by (-1)^i f(j) + (-1)^j g(i),
65  * which cannot represent the (-1)^(i+j) i j growth of the particular solution.
66  * The failure is silent, because checking that avg4(S) reproduces T is then
67  * satisfied by construction.
68  *
69  * ---------------------------------------------------------------------------
70  * What we do instead
71  * ---------------------------------------------------------------------------
72  * Let S_ref be the direct interpolation of T to the nodes (makeReference) and
73  * R = T - A S_ref the part of T that interpolation fails to reproduce. We
74  * solve for a *correction* E to the interpolant,
75  *
76  * min_E || A E - R ||^2 + lambda || V E ||^2 + mu || E ||^2 , S = S_ref + E,
77  *
78  * where V is a roughness operator (first derivative or Laplacian). The normal
79  * equations
80  *
81  * ( A^T A + lambda V^T V + mu I ) E = A^T R
82  *
83  * are symmetric positive definite for any mu > 0, so E exists, is unique, and
84  * -- unlike the exact inverse -- is bounded. Specifically, writing
85  * ( A^T A + lambda V^T V + mu I ) E = A^T R and pairing with E gives
86  *
87  * || A E ||^2 + lambda || V E ||^2 + mu || E ||^2 <= || A E || || R || ,
88  *
89  * and since 2 sqrt(mu) ||A E|| ||E|| <= ||A E||^2 + mu ||E||^2, we get the
90  * a priori bound
91  *
92  * || E || <= || R || / ( 2 sqrt(mu) ) .
93  *
94  * The correction is therefore controlled by how badly interpolation already
95  * fails, never by the conditioning of A.
96  *
97  * ---------------------------------------------------------------------------
98  * Choosing mu
99  * ---------------------------------------------------------------------------
100  * A single fixed mu cannot serve both smooth and rough terrain: small mu
101  * recovers well-resolved features almost exactly but lets rough fields drift
102  * far from the interpolant, and large mu does the reverse. We therefore start
103  * from a small mu and raise it until the correction satisfies the pointwise cap
104  *
105  * max |E| <= min( 2 max|R| , 1/4 (max T - min T) ) ,
106  *
107  * i.e. the nodes may not move away from the direct interpolation by more than
108  * twice the interpolation misfit, nor by more than a quarter of the relief of
109  * the data. Because ||E|| -> 0 as mu -> infinity the loop always terminates,
110  * and on failure we fall back to the interpolant itself. Smooth terrain keeps
111  * the smallest mu and is reproduced to well below the interpolation error;
112  * rough terrain is pulled back toward the interpolant instead of blowing up.
113  *
114  * The averaging error max |avg4(S) - T| returned in SolveInfo is now a genuine
115  * diagnostic -- it is no longer satisfied by construction -- and is reported
116  * alongside the same quantity for the plain interpolant and the range of S, so
117  * callers can sanity check the result.
118  */
120 {
121 public:
122  using Real = amrex::Real;
123 
124  explicit NodalReconstruction (amrex::Box const& face_box,
125  amrex::Geometry const& geom)
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  }
160 
161  [[nodiscard]] amrex::Box const& nodalBox () const noexcept {
162  return m_nodal_box;
163  }
164 
165  /**
166  * Direct interpolation of the cell-centered data to the nodes: the average
167  * of the (up to four) cells that touch each node, with edge and corner
168  * nodes taking the average of the cells that exist. This is the field the
169  * solve corrects, and the field it falls back to.
170  */
171  [[nodiscard]] amrex::FArrayBox makeReference (amrex::FArrayBox const& T) const
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  }
189 
190  /**
191  * Solve the regularized least-squares problem described above.
192  *
193  * \param T cell-centered data to be matched
194  * \param reference the interpolant to correct (see makeReference)
195  * \param var_op roughness operator used as the smoothness prior
196  * \param relative_tolerance CG stopping tolerance
197  * \param smoothing lambda; weight of the roughness penalty
198  * \param min_regularization smallest mu tried; raised by factors of four
199  * until the correction respects its cap
200  * \param max_iterations CG iteration cap per attempt
201  */
202  [[nodiscard]] std::pair<amrex::FArrayBox,SolveInfo>
203  solve (amrex::FArrayBox const& T,
204  amrex::FArrayBox const& reference,
205  VariationOperator var_op,
206  Real relative_tolerance = Real(1.e-10),
207  Real smoothing = Real(1.e-2),
208  Real min_regularization = Real(2.5e-4),
209  int max_iterations = 1000)
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  }
293 
294  //! max_ij | avg4(S) - T |
295  [[nodiscard]] Real maxAverageError (amrex::FArrayBox const& T,
296  amrex::FArrayBox const& S) const
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  }
310 
311  [[nodiscard]] Real totalSquaredVariation (amrex::FArrayBox const& S) const
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  }
330 
331 private:
332  //! The correction may not move a node further from the direct interpolation
333  //! than this multiple of the interpolation misfit ...
334  static constexpr amrex::Real deviation_factor = amrex::Real(2.0);
335  //! ... nor than this fraction of the relief of the data.
336  static constexpr amrex::Real relief_factor = amrex::Real(0.25);
337  //! Factor by which mu is raised when the cap is violated, and the number of
338  //! times we are willing to do so.
339  static constexpr amrex::Real mu_growth = amrex::Real(4.0);
340  static constexpr int max_attempts = 24;
341 
342  amrex::Box m_face_box;
343  amrex::Box m_nodal_box;
344  int m_ilo=0;
345  int m_ihi=-1;
346  int m_jlo=0;
347  int m_jhi=-1;
355 
356  mutable amrex::FArrayBox m_cc_scratch;
357  mutable amrex::FArrayBox m_nd_scratch;
358 
359  //! (A S)(i,j) = 1/4 [ S(i,j) + S(i+1,j) + S(i,j+1) + S(i+1,j+1) ]
360  void applyA (amrex::FArrayBox const& S,
361  amrex::FArrayBox& T) const
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  }
372 
373  //! Adjoint of applyA: scatter each cell value to its four nodes.
374  void applyAT (amrex::FArrayBox const& T,
375  amrex::FArrayBox& R) const
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  }
390 
391  void applyD (amrex::FArrayBox const& S,
392  amrex::FArrayBox& difx,
393  amrex::FArrayBox& dify) const
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  }
414 
415  void applyDT (amrex::FArrayBox const& difx,
416  amrex::FArrayBox const& dify,
417  amrex::FArrayBox& R) const
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  }
439 
440  void applyDTD (amrex::FArrayBox const& S,
441  amrex::FArrayBox& R) const
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  }
452 
453  void applyL (amrex::FArrayBox const& S,
454  amrex::FArrayBox& lap) const
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  }
472 
473  void applyLT (amrex::FArrayBox const& lap,
474  amrex::FArrayBox& R) const
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  }
492 
493  void applyLTL (amrex::FArrayBox const& S,
494  amrex::FArrayBox& R) const
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  }
509 
510  void applyVariationOperator (amrex::FArrayBox const& S,
511  amrex::FArrayBox& R) const
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  }
526 
527  //! R = ( A^T A + lambda V^T V + mu I ) S
528  void applyM (amrex::FArrayBox const& S,
529  amrex::FArrayBox& R) const
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  }
544 
545  /**
546  * Diagonal of M, assembled by accumulating the square of every stencil
547  * coefficient that touches a node. Used as a Jacobi preconditioner.
548  */
549  void computeDiagonal (amrex::FArrayBox& diag) const
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  }
596 
597  void computeDiagnostics (amrex::FArrayBox const& T,
598  amrex::FArrayBox const& S,
599  SolveInfo& info) const
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  }
614 
615  [[nodiscard]] Real maxAbs (amrex::FArrayBox const& F) const
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  }
626 
627  //! Jacobi-preconditioned conjugate gradient on the SPD operator M.
628  //! x is both the initial guess and the result; it is reset to zero here
629  //! because every attempt restarts the correction from the interpolant.
630  [[nodiscard]] SolveInfo conjugateGradient (amrex::FArrayBox const& rhs,
631  amrex::FArrayBox& x,
632  Real relative_tolerance,
633  int max_iterations) const
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  }
703 
704  void applyJacobi (amrex::FArrayBox const& diag,
705  amrex::FArrayBox const& r,
706  amrex::FArrayBox& z) const
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  }
717 
718  [[nodiscard]] Real dot (amrex::FArrayBox const& a,
719  amrex::FArrayBox const& b) const
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  }
731 };
732 
733 #endif
constexpr amrex::Real four
Definition: ERF_Constants.H:12
constexpr amrex::Real two
Definition: ERF_Constants.H:10
constexpr amrex::Real one
Definition: ERF_Constants.H:9
constexpr amrex::Real fourth
Definition: ERF_Constants.H:14
constexpr amrex::Real zero
Definition: ERF_Constants.H:8
Real value
Definition: ERF_HurricaneDiagnostics.cpp:30
const Real dy
Definition: ERF_InitCustomPert_ABL.H:24
const Real dx
Definition: ERF_InitCustomPert_ABL.H:23
Real T
Definition: ERF_InitCustomPert_Bubble.H:106
amrex::Real beta
Definition: ERF_InitCustomPert_DataAssimilation_ISV.H:10
VariationOperator
Definition: ERF_NodalReconstruction.H:19
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_NodalReconstruction.H:120
amrex::Real m_gyy
Definition: ERF_NodalReconstruction.H:351
void applyLTL(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:493
void applyDT(amrex::FArrayBox const &difx, amrex::FArrayBox const &dify, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:415
SolveInfo conjugateGradient(amrex::FArrayBox const &rhs, amrex::FArrayBox &x, Real relative_tolerance, int max_iterations) const
Definition: ERF_NodalReconstruction.H:630
amrex::Real m_lambda
Definition: ERF_NodalReconstruction.H:352
int m_ihi
Definition: ERF_NodalReconstruction.H:345
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
Real maxAverageError(amrex::FArrayBox const &T, amrex::FArrayBox const &S) const
max_ij | avg4(S) - T |
Definition: ERF_NodalReconstruction.H:295
amrex::Box const & nodalBox() const noexcept
Definition: ERF_NodalReconstruction.H:161
VariationOperator m_var_op
Definition: ERF_NodalReconstruction.H:354
void applyJacobi(amrex::FArrayBox const &diag, amrex::FArrayBox const &r, amrex::FArrayBox &z) const
Definition: ERF_NodalReconstruction.H:704
int m_ilo
Definition: ERF_NodalReconstruction.H:344
void computeDiagonal(amrex::FArrayBox &diag) const
Definition: ERF_NodalReconstruction.H:549
Real totalSquaredVariation(amrex::FArrayBox const &S) const
Definition: ERF_NodalReconstruction.H:311
amrex::FArrayBox makeReference(amrex::FArrayBox const &T) const
Definition: ERF_NodalReconstruction.H:171
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
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
static constexpr amrex::Real relief_factor
... nor than this fraction of the relief of the data.
Definition: ERF_NodalReconstruction.H:336
void applyD(amrex::FArrayBox const &S, amrex::FArrayBox &difx, amrex::FArrayBox &dify) const
Definition: ERF_NodalReconstruction.H:391
amrex::Real m_gxx
Definition: ERF_NodalReconstruction.H:350
amrex::FArrayBox m_nd_scratch
Definition: ERF_NodalReconstruction.H:357
Real maxAbs(amrex::FArrayBox const &F) const
Definition: ERF_NodalReconstruction.H:615
void applyLT(amrex::FArrayBox const &lap, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:473
static constexpr amrex::Real mu_growth
Definition: ERF_NodalReconstruction.H:339
amrex::Box m_nodal_box
Definition: ERF_NodalReconstruction.H:343
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)
Definition: ERF_NodalReconstruction.H:203
amrex::FArrayBox m_cc_scratch
Definition: ERF_NodalReconstruction.H:356
amrex::Real m_mu
Definition: ERF_NodalReconstruction.H:353
int m_jlo
Definition: ERF_NodalReconstruction.H:346
static constexpr amrex::Real deviation_factor
Definition: ERF_NodalReconstruction.H:334
void applyVariationOperator(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:510
void computeDiagnostics(amrex::FArrayBox const &T, amrex::FArrayBox const &S, SolveInfo &info) const
Definition: ERF_NodalReconstruction.H:597
amrex::Real m_gx
Definition: ERF_NodalReconstruction.H:348
NodalReconstruction(amrex::Box const &face_box, amrex::Geometry const &geom)
Definition: ERF_NodalReconstruction.H:124
Real dot(amrex::FArrayBox const &a, amrex::FArrayBox const &b) const
Definition: ERF_NodalReconstruction.H:718
amrex::Real m_gy
Definition: ERF_NodalReconstruction.H:349
void applyL(amrex::FArrayBox const &S, amrex::FArrayBox &lap) const
Definition: ERF_NodalReconstruction.H:453
amrex::Real Real
Definition: ERF_NodalReconstruction.H:122
amrex::Box m_face_box
Definition: ERF_NodalReconstruction.H:342
int m_jhi
Definition: ERF_NodalReconstruction.H:347
void applyDTD(amrex::FArrayBox const &S, amrex::FArrayBox &R) const
Definition: ERF_NodalReconstruction.H:440
static constexpr int max_attempts
Definition: ERF_NodalReconstruction.H:340
@ qc
Definition: ERF_SatAdj.H:40
@ R
Definition: ERF_IndexDefines.H:127
@ q
Definition: ERF_WSM6.H:184
@ p
Definition: ERF_WSM6.H:191
@ qa
Definition: ERF_AdvanceWSM6.cpp:136
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
int iterations
total CG iterations, summed over attempts
Definition: ERF_NodalReconstruction.H:25
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
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