ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_ImplicitDiff_S.cpp File Reference
Include dependency graph for ERF_ImplicitDiff_S.cpp:

Macros

#define INSTANTIATE_IMPLICIT_DIFF_FOR_MOM_LU(STAGDIR)
 

Functions

void ImplicitDiffForStateLU_S (const Box &bx, const Box &domain, const int level, const int n, const double dt_d, const GpuArray< Real, AMREX_SPACEDIM *2 > &bc_neumann_vals, const Array4< Real > &cell_data, const Gpu::DeviceVector< Real > &stretched_dz_d, const Array4< const Real > &scalar_zflux, const Array4< const Real > &mu_turb, const SolverChoice &solverChoice, const BCRec *bc_ptr, const bool use_SurfLayer, const Real implicit_fac, const bool use_mrf_countergradient)
 
template<int stagdir>
void ImplicitDiffForMomLU_S (const Box &bx, const Box &domain, const int level, const double dt_d, const Array4< const int > &col_kext, const Array4< const Real > &cell_data, const Array4< Real > &face_data, const Array4< const Real > &tau, const Array4< const Real > &tau_corr, const Gpu::DeviceVector< Real > &stretched_dz_d, const Array4< const Real > &mu_turb, const SolverChoice &solverChoice, const BCRec *bc_ptr, const bool use_SurfLayer, const Real implicit_fac, const bool use_ysu_mom_countergradient)
 

Macro Definition Documentation

◆ INSTANTIATE_IMPLICIT_DIFF_FOR_MOM_LU

#define INSTANTIATE_IMPLICIT_DIFF_FOR_MOM_LU (   STAGDIR)
Value:
template void ImplicitDiffForMomLU_S<STAGDIR> ( \
const Box&, \
const Box&, \
const int, \
const double, \
const Array4<const int >&, \
const Array4<const Real>&, \
const Array4< Real>&, \
const Array4<const Real>&, \
const Array4<const Real>&, \
const Gpu::DeviceVector<Real>&, \
const Array4<const Real>&, \
const SolverChoice&, \
const BCRec*, \
const bool, \
const Real, \
const bool);
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_DataStruct.H:662

Function Documentation

◆ ImplicitDiffForMomLU_S()

template<int stagdir>
void ImplicitDiffForMomLU_S ( const Box &  bx,
const Box &  domain,
const int  level,
const double  dt_d,
const Array4< const int > &  col_kext,
const Array4< const Real > &  cell_data,
const Array4< Real > &  face_data,
const Array4< const Real > &  tau,
const Array4< const Real > &  tau_corr,
const Gpu::DeviceVector< Real > &  stretched_dz_d,
const Array4< const Real > &  mu_turb,
const SolverChoice solverChoice,
const BCRec *  bc_ptr,
const bool  use_SurfLayer,
const Real  implicit_fac,
const bool  use_ysu_mom_countergradient 
)

Function for computing the implicit contribution to the vertical diffusion of momentum, with a vertically stretched grid over flat terrain.

This function (explicitly instantiated below) handles staggering in x, y, or z through the template parameter, stagdir.

Parameters
[in]bxcell-centered box to loop over
[in]levelAMR level
[in]dt_dtime step
[in]cell_dataconserved cell-centered rho
[in,out]face_dataconserved momentum
[in]taustress contribution to momentum
[in]tau_corrstress contribution to momentum that will be corrected by the implicit solve
[in]stretched_dz_darray over z of dz[k]
[in]mu_turbturbulent viscosity
[in]solverChoicecontainer of parameters
[in]bc_ptrcontainer with boundary conditions
[in]use_SurfLayerwhether we have turned on subgrid diffusion
[in]implicit_facif 1 then fully implicit; if 0 then fully explicit
[in]use_ysu_mom_countergradientwhether to include YSU momentum countergradient correction
314 {
315  BL_PROFILE_VAR("ImplicitDiffForMom_S()",ImplicitDiffForMom_S);
316 
317  Real dt = static_cast<Real>(dt_d);
318 
319  // setup quantities for getRhoAlphaAtFaces()
320  DiffChoice dc = solverChoice.diffChoice;
321  TurbChoice tc = solverChoice.turbChoice[level];
322  bool l_consA = (dc.molec_diff_type == MolecDiffType::ConstantAlpha);
323  bool l_turb = tc.use_kturb;
324  // The off-diagonal correction strains for u/v contain a factor of 1/2,
325  // while the diagonal correction strain for w does not.
326  constexpr Real molec_fac = (stagdir == 2) ? two : one;
327  Real mu_eff = (l_consA) ? molec_fac * dc.dynamic_viscosity / dc.rho0_trans
328  : molec_fac * dc.dynamic_viscosity;
329 
330  // g(S*) coefficient
331  // stagdir==0: tau_corr = myhalf * du/dz * mu_tot
332  // stagdir==1: tau_corr = myhalf * dv/dz * mu_tot
333  // stagdir==2: tau_corr = dw/dz * mu_tot
334  constexpr Real gfac = (stagdir == 2) ? two/three : one;
335 
336  // offsets used to average to faces
337  constexpr int ioff = (stagdir == 0) ? 1 : 0;
338  constexpr int joff = (stagdir == 1) ? 1 : 0;
339 
340  // Box bounds
341  int ilo = bx.smallEnd(0);
342  int ihi = bx.bigEnd(0);
343  int jlo = bx.smallEnd(1);
344  int jhi = bx.bigEnd(1);
345  int box_klo = bx.smallEnd(2);
346  int box_khi = bx.bigEnd(2);
347  amrex::ignore_unused(ilo, ihi, jlo, jhi);
348 
349  // Vertical staggering of this component, and the vertical extent of the domain for the
350  // faces we are solving on. A column whose top or bottom is not the domain boundary ends
351  // at a coarse/fine boundary instead, and must not be given a physical BC row.
352  constexpr int knodal = (stagdir == 2) ? 1 : 0;
353  const int dom_klo = domain.smallEnd(2);
354  const int dom_khi = domain.bigEnd(2) + knodal;
355 
356  // Temporary FABs for tridiagonal solve (allocated on column)
357  // A[k] * x[k-1] + B[k] * x[k] + C[k+1] = RHS[k]
358  amrex::FArrayBox RHS_fab, soln_fab, coeffG_fab;
359  RHS_fab.resize(bx,1, amrex::The_Async_Arena());
360  soln_fab.resize(bx,1, amrex::The_Async_Arena());
361  coeffG_fab.resize(bx,1, amrex::The_Async_Arena());
362  auto const& RHS_a = RHS_fab.array();
363  auto const& soln_a = soln_fab.array();
364  auto const& coeffG_a = coeffG_fab.array();
365 
366  auto dz_ptr = stretched_dz_d.data();
367 
368  int bc_comp = BCVars::xvel_bc + stagdir;
369  bool ext_dir_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::ext_dir ||
370  bc_ptr[bc_comp].lo(2) == ERFBCType::ext_dir_prim);
371  bool ext_dir_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::ext_dir ||
372  bc_ptr[bc_comp].hi(2) == ERFBCType::ext_dir_prim);
373  bool foextrap_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::foextrap);
374  bool foextrap_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::foextrap);
375  amrex::ignore_unused(foextrap_on_zlo,foextrap_on_zhi);
376 
377  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zlo || ext_dir_on_zlo || use_SurfLayer,
378  "Unexpected lower BC for momentum used with implicit vertical diffusion");
379  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zhi || ext_dir_on_zhi,
380  "Unexpected upper BC for momentum used with implicit vertical diffusion");
381 
382  Real Fact = implicit_fac * dt;
383 
384 #ifdef AMREX_USE_GPU
385  ParallelFor(makeSlab(bx,2,0), [=] AMREX_GPU_DEVICE (int i, int j, int)
386  {
387 #else
388  for (int j(jlo); j<=jhi; ++j) {
389  for (int i(ilo); i<=ihi; ++i) {
390 #endif
391 
392  // The face column at (i,j) is shared by the two cell columns on either side of it,
393  // and so by the box on either side of a grid seam. Solve only over the vertical
394  // range that both of those cell columns cover: the boxes on the two sides then
395  // build the same tridiagonal system and get the same answer, so the duplicated
396  // faces along the seam stay consistent. Faces outside that range sit on a lateral
397  // coarse/fine boundary and are set from the coarse level, so it is consistent to
398  // leave them to the explicit update.
399  const int klo = amrex::max(box_klo, col_kext(i-ioff,j-joff,0,0),
400  col_kext(i ,j ,0,0));
401  const int khi = amrex::min(box_khi, col_kext(i-ioff,j-joff,0,1)+knodal,
402  col_kext(i ,j ,0,1)+knodal);
403 
404  // Fewer than two faces in common -- nothing to invert, leave the column explicit
405 #ifdef AMREX_USE_GPU
406  if (khi <= klo) { return; }
407 #else
408  if (khi <= klo) { continue; }
409 #endif
410  // Notes:
411  //
412  // - In DiffusionSrcForMom (e.g., for x-mom)
413  //
414  // Real diffContrib = ...
415  // + (tau13(i,j,k+1) - tau13(i,j,k)) / dzinv
416  // rho_u_rhs(i,j,k) -= diffContrib; // note the negative sign
417  //
418  // - We need to scale the explicit _part_ of `tau13` (for x-mom) by (1 - implicit_fac)
419  // The part that needs to be scaled is stored in `tau_corr`.
420  // E.g., tau13 = myhalf * (du/dz + dw/dx)
421  // tau13_corr = myhalf * du/dz
422  //
423  // - The momentum (`face_data`) was set to `S_old + S_rhs * dt`
424  // prior to including "ERF_Implicit.H". Recall that S_rhs includes
425  // sources from advection and other forcings, not just diffusion.
426  //
427  // - To correct momentum, we need to subtract `implicit_fac * diffContrib_corr`
428  // from S_rhs to recover `(1 - implicit_fac) * diffContrib_corr`,
429  // where `diffContrib_corr = -d(tau_corr)/dz`. The negative sign
430  // comes from our convention for the RHS diffusion source.
431  //
432  // Subtracting a negative gives the += below; multiply by dt to
433  // get the intermediate momentum on the RHS of the tridiagonal
434  // system.
435  //
436  // - With a surface_layer BC, tau13/23 holds the vertical flux -d_z(k*u_i)
437  // directly. We must use tau at klo (not tau_corr) with SL BCs.
438  //
439  // - Finally, the terms ~ RHS += (tau_corr_hi - tau_corr_lo) / dz (below)
440  // essentially undo the explicit diffusion update that will be
441  // handled here implicitly.
442 
443  // Bottom boundary coefficients and RHS for L decomp
444  //===================================================
445  Real rhoface, rhoAlpha_lo, rhoAlpha_hi;
446  Real dz_inv, dz_inv_lo, dz_inv_hi;
447  Real a_tmp, b_tmp, c_tmp, inv_b2_tmp;
448  {
449  rhoface = myhalf * (cell_data(i,j,klo,Rho_comp) + cell_data(i-ioff,j-joff,klo,Rho_comp));
450  getRhoAlphaForFaces(i, j, klo, ioff, joff, rhoAlpha_lo, rhoAlpha_hi,
451  cell_data, mu_turb, mu_eff,
452  l_consA, l_turb);
453 
454  dz_inv = one / dz_ptr[klo];
455  dz_inv_lo = dz_inv;
456  dz_inv_hi = two / (dz_ptr[klo] + dz_ptr[klo+1]);
457 
458  a_tmp = zero;
459  c_tmp = -Fact * gfac * rhoAlpha_hi * dz_inv_hi * dz_inv;
460 
461  RHS_a(i,j,klo) = face_data(i,j,klo); // NOTE: this is momenta; solution is velocity
462 
463  // BCs: Dirichlet (u_i = val), slip wall (w = 0), or surface layer (w = 0) --
464  // but only where the bottom of this column really is the domain boundary
465  if (klo != dom_klo) {
466  // Coarse/fine (or grid) boundary: close the system with the known face value
467  // below instead of imposing a physical BC at an interior height
468  dz_inv_lo = two / (dz_ptr[klo] + dz_ptr[klo-1]);
469  a_tmp = -Fact * rhoAlpha_lo * dz_inv_lo * dz_inv;
470  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau_corr(i,j,klo)) * dz_inv;
471  const Real rho_below = myhalf * ( cell_data(i ,j ,klo-1,Rho_comp)
472  + cell_data(i-ioff,j-joff,klo-1,Rho_comp) );
473  RHS_a(i,j,klo) -= a_tmp * (face_data(i,j,klo-1) / rho_below);
474  } else if (ext_dir_on_zlo) {
475  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau_corr(i,j,klo)) * dz_inv;
476  if (stagdir==2) {
477  c_tmp = 0.;
478  RHS_a(i,j,klo) = 0.;
479  } else {
480  // NOTE: wall is 1/2 dz away (2 dz_inv)
481  a_tmp = -two * Fact * rhoAlpha_lo * dz_inv_lo * dz_inv;
482  const Real rho_wall = myhalf * ( cell_data(i ,j ,klo-1,Rho_comp)
483  + cell_data(i-ioff,j-joff,klo-1,Rho_comp) );
484  const Real wall_velocity = face_data(i,j,klo-1) / rho_wall;
485  RHS_a(i,j,klo) -= a_tmp * wall_velocity;
486  }
487  } else if (use_SurfLayer) {
488  // NOTE: tau = -mu*d_z(u_i) w/ SL
489  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau(i,j,klo)) * dz_inv;
490  RHS_a(i,j,klo) += Fact * dz_inv * tau(i,j,klo);
491  } else {
492  // NOTE: FOEXTRAP has zero lower flux (nothing to add to RHS)
493  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau_corr(i,j,klo)) * dz_inv;
494  }
495 
496  // Add YSU momentum countergradient correction at bottom boundary.
497  // NOTE: As for the scalars, the lower face at klo carries no
498  // countergradient flux -- the surface stress is supplied by the
499  // surface layer model or the wall BC above. Only the upper face
500  // contributes here.
501  // NOTE: The sign matches the scalar path: tau_i3 = -rho*K*(du_i/dz - gamma_i),
502  // so the countergradient piece of the flux is +rho*K*gamma_i and its
503  // divergence enters the RHS with a minus sign.
504  if (use_ysu_mom_countergradient && stagdir < 2) {
505  const int hgam_comp = (stagdir == 0) ? EddyDiff::HGAMU_v : EddyDiff::HGAMV_v;
506  // Average HGAM* to the staggered face
507  const Real gam_klo = myhalf * (mu_turb(i,j,klo ,hgam_comp) + mu_turb(i-ioff,j-joff,klo ,hgam_comp));
508  const Real gam_kp1 = myhalf * (mu_turb(i,j,klo+1,hgam_comp) + mu_turb(i-ioff,j-joff,klo+1,hgam_comp));
509  const Real gam_hi = myhalf * (gam_klo + gam_kp1);
510  RHS_a(i,j,klo) -= Fact * dz_inv * rhoAlpha_hi * gam_hi;
511  }
512 
513  b_tmp = rhoface - a_tmp - c_tmp;
514  inv_b2_tmp = one;
515 
516  RHS_a(i,j,klo) /= b_tmp; // NOTE: this is now "rho"
517  coeffG_a(i,j,klo) = c_tmp / b_tmp; // NOTE: this is now "gamma"
518  }
519 
520  // Build the coefficients and RHS for L decomp
521  //===================================================
522  for (int k(klo+1); k < khi; k++) {
523  rhoface = myhalf * (cell_data(i,j,k,Rho_comp) + cell_data(i-ioff,j-joff,k,Rho_comp));
524  getRhoAlphaForFaces(i, j, k, ioff, joff, rhoAlpha_lo, rhoAlpha_hi,
525  cell_data, mu_turb, mu_eff,
526  l_consA, l_turb);
527 
528  dz_inv = one / dz_ptr[k];
529  dz_inv_lo = two / (dz_ptr[k] + dz_ptr[k-1]);
530  dz_inv_hi = two / (dz_ptr[k] + dz_ptr[k+1]);
531 
532  a_tmp = -Fact * rhoAlpha_lo * dz_inv_lo * dz_inv;
533  c_tmp = -Fact * rhoAlpha_hi * dz_inv_hi * dz_inv;
534  b_tmp = rhoface - a_tmp - c_tmp;
535  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,k-1));
536 
537  RHS_a(i,j,k) = face_data(i,j,k); // NOTE: this is momenta; solution is velocity
538  RHS_a(i,j,k) += Fact * gfac * (tau_corr(i,j,k+1) - tau_corr(i,j,k)) * dz_inv;
539 
540  // Add YSU momentum countergradient correction
541  if (use_ysu_mom_countergradient && stagdir < 2) {
542  const int hgam_comp = (stagdir == 0) ? EddyDiff::HGAMU_v : EddyDiff::HGAMV_v;
543  // AverageHGAM* to the staggered face
544  const Real gam_k = myhalf * (mu_turb(i,j,k ,hgam_comp) + mu_turb(i-ioff,j-joff,k ,hgam_comp));
545  const Real gam_km1 = myhalf * (mu_turb(i,j,k-1,hgam_comp) + mu_turb(i-ioff,j-joff,k-1,hgam_comp));
546  const Real gam_kp1 = myhalf * (mu_turb(i,j,k+1,hgam_comp) + mu_turb(i-ioff,j-joff,k+1,hgam_comp));
547  const Real gam_hi = myhalf * (gam_k + gam_kp1); // at k+1/2
548  const Real gam_lo = myhalf * (gam_k + gam_km1); // at k-1/2
549  RHS_a(i,j,k) -= Fact * dz_inv * (rhoAlpha_hi * gam_hi - rhoAlpha_lo * gam_lo);
550  }
551 
552  RHS_a(i,j,k) = (RHS_a(i,j,k) - a_tmp * RHS_a(i,j,k-1)) * inv_b2_tmp; // NOTE: This is now "rho"
553  coeffG_a(i,j,k) = c_tmp * inv_b2_tmp; // NOTE: this is now "gamma"
554  } // k
555 
556  // Top boundary coefficients and RHS for L decomp
557  //===================================================
558  {
559  rhoface = myhalf * (cell_data(i,j,khi,Rho_comp) + cell_data(i-ioff,j-joff,khi,Rho_comp));
560  getRhoAlphaForFaces(i, j, khi, ioff, joff, rhoAlpha_lo, rhoAlpha_hi,
561  cell_data, mu_turb, mu_eff,
562  l_consA, l_turb);
563 
564  dz_inv = one / dz_ptr[khi];
565  dz_inv_lo = two / (dz_ptr[khi] + dz_ptr[khi-1]);
566  dz_inv_hi = dz_inv;
567 
568  a_tmp = -Fact * gfac * rhoAlpha_lo * dz_inv_lo * dz_inv;
569  c_tmp = zero;
570 
571  RHS_a(i,j,khi) = face_data(i,j,khi); // NOTE: this is momenta; solution is velocity
572  RHS_a(i,j,khi) += Fact * gfac * (tau_corr(i,j,khi+1) - tau_corr(i,j,khi)) * dz_inv;
573 
574  // BCs: Dirichlet (u_i = val), slip wall (w = 0) --
575  // but only where the top of this column really is the domain boundary
576  if (khi != dom_khi) {
577  // Coarse/fine (or grid) boundary: close the system with the known face value
578  // above instead of imposing a physical BC at an interior height
579  dz_inv_hi = two / (dz_ptr[khi] + dz_ptr[khi+1]);
580  c_tmp = -Fact * rhoAlpha_hi * dz_inv_hi * dz_inv;
581  const Real rho_above = myhalf * ( cell_data(i ,j ,khi+1,Rho_comp)
582  + cell_data(i-ioff,j-joff,khi+1,Rho_comp) );
583  RHS_a(i,j,khi) -= c_tmp * (face_data(i,j,khi+1) / rho_above);
584  } else if (ext_dir_on_zhi) {
585  if (stagdir==2) {
586  a_tmp = zero;
587  RHS_a(i,j,khi) = zero;
588  } else {
589  // NOTE: wall is 1/2 dz away (2 dz_inv)
590  c_tmp = -two * Fact * rhoAlpha_hi * dz_inv_hi * dz_inv;
591  const Real rho_wall = myhalf * ( cell_data(i ,j ,khi+1,Rho_comp)
592  + cell_data(i-ioff,j-joff,khi+1,Rho_comp) );
593  const Real wall_velocity = face_data(i,j,khi+1) / rho_wall;
594  RHS_a(i,j,khi) -= c_tmp * wall_velocity;
595  }
596  }
597 
598  b_tmp = rhoface - a_tmp - c_tmp;
599  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,khi-1));
600 
601  // First solve
602  soln_a(i,j,khi) = (RHS_a(i,j,khi) - a_tmp * RHS_a(i,j,khi-1)) * inv_b2_tmp;
603  }
604 
605  // Back sweep the U decomp solution
606  //===================================================
607  for (int k(khi-1); k>=klo; --k) {
608  soln_a(i,j,k) = RHS_a(i,j,k) - coeffG_a(i,j,k) * soln_a(i,j,k+1);
609  }
610 
611  // Convert back to momenta
612  //===================================================
613  for (int k(klo); k<=khi; ++k) {
614  rhoface = myhalf * (cell_data(i,j,k,Rho_comp) + cell_data(i-ioff,j-joff,k,Rho_comp));
615  face_data(i,j,k) = rhoface * soln_a(i,j,k);
616  }
617 
618 #ifdef AMREX_USE_GPU
619  });
620 #else
621  } // i
622  } // j
623 #endif
624 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void getRhoAlphaForFaces(int i, int j, int k, int ioff, int joff, amrex::Real &rhoAlpha_lo, amrex::Real &rhoAlpha_hi, const amrex::Array4< const amrex::Real > &cell_data, const amrex::Array4< const amrex::Real > &mu_turb, const amrex::Real mu_eff, bool l_consA, bool l_turb)
Definition: ERF_GetRhoAlphaForFaces.H:22
#define Rho_comp
Definition: ERF_IndexDefines.H:39
const int klo
Definition: ERF_InitCustomPert_ABL.H:75
const int khi
Definition: ERF_InitCustomPert_Bubble.H:21
ParallelFor(fab_box, [=] AMREX_GPU_DEVICE(int i, int j, int k) { qrcuten_arr(i, j, k)=Real(0);qscuten_arr(i, j, k)=Real(0);qicuten_arr(i, j, k)=Real(0);})
constexpr amrex::Real three
Definition: ERF_NumericalConstants.H:32
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:31
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:30
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:29
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:34
AMREX_ASSERT_WITH_MESSAGE(wbar_cutoff_min > wbar_cutoff_max, "ERROR: wbar_cutoff_min < wbar_cutoff_max")
@ xvel_bc
Definition: ERF_IndexDefines.H:105
@ foextrap
Definition: ERF_IndexDefines.H:296
@ ext_dir
Definition: ERF_IndexDefines.H:297
@ ext_dir_prim
Definition: ERF_IndexDefines.H:300
@ HGAMU_v
Definition: ERF_IndexDefines.H:262
@ HGAMV_v
Definition: ERF_IndexDefines.H:263
Definition: ERF_DiffStruct.H:22
MolecDiffType molec_diff_type
Selected molecular transport model.
Definition: ERF_DiffStruct.H:94
amrex::Real dynamic_viscosity
Dynamic viscosity for momentum diffusion [kg/(m-s)].
Definition: ERF_DiffStruct.H:106
amrex::Real rho0_trans
Reference density used to compute dynamic diffusion coefficients [kg/m3].
Definition: ERF_DiffStruct.H:101
amrex::Vector< TurbChoice > turbChoice
Turbulence options for each AMR level.
Definition: ERF_DataStruct.H:1974
DiffChoice diffChoice
Diffusion-related options.
Definition: ERF_DataStruct.H:1971
Definition: ERF_TurbStruct.H:115
bool use_kturb
Whether any turbulence model is active.
Definition: ERF_TurbStruct.H:835
Here is the call graph for this function:

◆ ImplicitDiffForStateLU_S()

void ImplicitDiffForStateLU_S ( const Box &  bx,
const Box &  domain,
const int  level,
const int  n,
const double  dt_d,
const GpuArray< Real, AMREX_SPACEDIM *2 > &  bc_neumann_vals,
const Array4< Real > &  cell_data,
const Gpu::DeviceVector< Real > &  stretched_dz_d,
const Array4< const Real > &  scalar_zflux,
const Array4< const Real > &  mu_turb,
const SolverChoice solverChoice,
const BCRec *  bc_ptr,
const bool  use_SurfLayer,
const Real  implicit_fac,
const bool  use_mrf_countergradient 
)

Function for computing the implicit contribution to the vertical diffusion of theta, with a vertically stretched grid over flat terrain.

Parameters
[in]bxcell-centered box to loop over
[in]domainbox of the whole domain
[in]levelAMR level
[in]nconserved component index
[in]dt_dtime step
[in]bc_neumann_valsvalues of derivatives if bc_type == Neumann
[in,out]cell_dataconserved cell-centered rho, rho theta
[in]stretched_dz_darray over z of dz[k]
[in]scalar_zfluxscalar vertical flux in z-dir
[in]mu_turbturbulent viscosity
[in]solverChoicecontainer of parameters
[in]bc_ptrcontainer with boundary conditions
[in]use_SurfLayerwhether we have turned on subgrid diffusion
[in]implicit_facif 1 then fully implicit; if 0 then fully explicit
[in]use_mrf_countergradientwhether to include MRF countergradient correction
45 {
46  BL_PROFILE_VAR("ImplicitDiffForState_S()",ImplicitDiffForState_S);
47 
48  Real dt = static_cast<Real>(dt_d);
49 
50  // setup quantities for getRhoAlpha()
51 #include "ERF_SetupVertDiff.H"
52  const int qty_index = n;
53  const int prim_index = qty_index - 1;
54  const int prim_scal_index = (qty_index >= RhoScalar_comp && qty_index < RhoScalar_comp+NSCALARS) ? PrimScalar_comp : prim_index;
55 
56  // Box bounds
57  int ilo = bx.smallEnd(0);
58  int ihi = bx.bigEnd(0);
59  int jlo = bx.smallEnd(1);
60  int jhi = bx.bigEnd(1);
61  int klo = bx.smallEnd(2);
62  int khi = bx.bigEnd(2);
63  amrex::ignore_unused(ilo, ihi, jlo, jhi);
64 
65  // Temporary FABs for tridiagonal solve (allocated on column)
66  // A[k] * x[k-1] + B[k] * x[k] + C[k+1] = RHS[k]
67 
68  // With LU decomposition, M * x = r is written as L * U * x = r with U * x = rho
69  // We then first have L * rho = r and U * x = rho
70  amrex::FArrayBox RHS_fab, soln_fab, coeffG_fab;
71  RHS_fab.resize(bx,1, amrex::The_Async_Arena());
72  soln_fab.resize(bx,1, amrex::The_Async_Arena());
73  coeffG_fab.resize(bx,1, amrex::The_Async_Arena());
74  auto const& RHS_a = RHS_fab.array();
75  auto const& soln_a = soln_fab.array();
76  auto const& coeffG_a = coeffG_fab.array();
77 
78  auto dz_ptr = stretched_dz_d.data();
79 
80  int bc_comp = qty_index;
81  bool foextrap_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::foextrap);
82  bool foextrap_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::foextrap);
83  bool neumann_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::neumann);
84  bool neumann_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::neumann);
85  amrex::ignore_unused(foextrap_on_zlo, foextrap_on_zhi);
86 
87  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zlo || neumann_on_zlo || use_SurfLayer,
88  "Unexpected lower BC for scalars used with implicit vertical diffusion");
89  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zhi || neumann_on_zhi,
90  "Unexpected upper BC for scalars used with implicit vertical diffusion");
91 
92  // k-eqn RANS with a Dirichlet wall value of k: the first cell is held at
93  // the value set by the surface layer, so its row reduces to x(klo) = phi(klo)
94  // and the row above sees it as a Dirichlet neighbour.
95  // NOTE: init_bcs requires zlo.type = surface_layer whenever dirichlet_k is set,
96  // so this matches the l_dirichlet_k pin in erf_slow_rhs_post, which also
97  // requires a surface layer. Do not relax one without the other.
98  const bool pin_klo = (qty_index == RhoKE_comp) && (klo == domain.smallEnd(2)) &&
99  (solverChoice.turbChoice[level].rans_type == RANSType::kEqn) &&
100  solverChoice.turbChoice[level].dirichlet_k;
101 
102  // A column whose bottom or top is not the domain boundary ends at a coarse/fine boundary
103  // instead. There the system must be closed with the value on the other side of that
104  // boundary rather than given a physical BC row at an interior height, so that the answer
105  // does not depend on where the grid happens to end in the vertical.
106  const bool at_zlo = (klo == domain.smallEnd(2));
107  const bool at_zhi = (khi == domain.bigEnd(2));
108 
109  Real Fact = implicit_fac * dt;
110 
111 #ifdef AMREX_USE_GPU
112  ParallelFor(makeSlab(bx,2,0), [=] AMREX_GPU_DEVICE (int i, int j, int)
113  {
114 #else
115  for (int j(jlo); j<=jhi; ++j) {
116  for (int i(ilo); i<=ihi; ++i) {
117 #endif
118  // Bottom boundary coefficients and RHS for L decomp
119  //===================================================
120  Real rhoAlpha_lo, rhoAlpha_hi;
121  Real dz_inv, dz_inv_lo, dz_inv_hi;
122  Real a_tmp, b_tmp, c_tmp, inv_b2_tmp;
123  {
124  getRhoAlpha(i, j, klo, rhoAlpha_lo, rhoAlpha_hi,
125  cell_data, mu_turb, d_alpha_eff, d_eddy_diff_idz,
126  prim_index, prim_scal_index, l_consA, l_turb);
127 
128  dz_inv = one / dz_ptr[klo];
129  dz_inv_lo = dz_inv;
130  dz_inv_hi = two / (dz_ptr[klo] + dz_ptr[klo+1]);
131 
132  a_tmp = zero;
133  if (!at_zlo) {
134  dz_inv_lo = two / (dz_ptr[klo] + dz_ptr[klo-1]);
135  a_tmp = -Fact * rhoAlpha_lo * dz_inv_lo * dz_inv;
136  }
137  c_tmp = (pin_klo) ? zero : -Fact * rhoAlpha_hi * dz_inv_hi * dz_inv;
138  b_tmp = cell_data(i,j,klo,Rho_comp) - a_tmp - c_tmp;
139  inv_b2_tmp = one;
140 
141  RHS_a(i,j,klo) = cell_data(i,j,klo,n); // NOTE: this is rho*phi; solution is phi
142  if (!at_zlo) {
143  // Coarse/fine boundary: close the system with the known value below
144  RHS_a(i,j,klo) -= a_tmp * (cell_data(i,j,klo-1,n) / cell_data(i,j,klo-1,Rho_comp));
145  } else if (pin_klo) {
146  // Dirichlet row: no flux terms
147  } else if (use_SurfLayer && scalar_zflux) {
148  RHS_a(i,j,klo) += Fact * dz_inv * scalar_zflux(i,j,klo); // NOTE: scalar_zflux = -K*d_z(\phi)
149  } else if (neumann_on_zlo) {
150  RHS_a(i,j,klo) += -Fact * dz_inv * rhoAlpha_lo * bc_neumann_vals[2]; // NOTE: N_val = d_z(\phi)
151  }
152 
153  // Add countergradient correction to RHS at bottom boundary.
154  // NOTE: The lower face at klo carries no countergradient flux -- the
155  // total surface flux is supplied by the surface layer model or the
156  // Neumann BC, while gamma represents nonlocal transport interior to
157  // the PBL. Only the upper face contributes here.
158  if (use_mrf_countergradient && (n == RhoTheta_comp || n == RhoQ1_comp)) {
159  const int gam_comp = (n == RhoTheta_comp) ? EddyDiff::HGAMT_v : EddyDiff::HGAMQ_v;
160  const Real gam_hi = myhalf * (mu_turb(i, j, klo, gam_comp) + mu_turb(i, j, klo+1, gam_comp));
161  // rhoAlpha*gam is already a flux, so its divergence over cell klo
162  // is scaled by the *cell* spacing, not the face spacing
163  RHS_a(i,j,klo) -= Fact * dz_inv * rhoAlpha_hi * gam_hi;
164  if (!at_zlo) {
165  const Real gam_lo = myhalf * (mu_turb(i, j, klo, gam_comp) + mu_turb(i, j, klo-1, gam_comp));
166  RHS_a(i,j,klo) += Fact * dz_inv * rhoAlpha_lo * gam_lo;
167  }
168  }
169 
170  RHS_a(i,j,klo) /= b_tmp; // NOTE: this is now "rho"
171  coeffG_a(i,j,klo) = c_tmp / b_tmp; // NOTE: this is now "gamma"
172  }
173 
174  // Build the coefficients and RHS for L decomp
175  //===================================================
176  for (int k(klo+1); k < khi; k++) {
177  getRhoAlpha(i, j, k, rhoAlpha_lo, rhoAlpha_hi,
178  cell_data, mu_turb, d_alpha_eff, d_eddy_diff_idz,
179  prim_index, prim_scal_index, l_consA, l_turb);
180 
181  dz_inv = one / dz_ptr[k];
182  dz_inv_lo = two / (dz_ptr[k] + dz_ptr[k-1]);
183  dz_inv_hi = two / (dz_ptr[k] + dz_ptr[k+1]);
184 
185  a_tmp = -Fact * rhoAlpha_lo * dz_inv_lo * dz_inv;
186  c_tmp = -Fact * rhoAlpha_hi * dz_inv_hi * dz_inv;
187  b_tmp = cell_data(i,j,k,Rho_comp) - a_tmp - c_tmp;
188  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,k-1));
189 
190  RHS_a(i,j,k) = cell_data(i,j,k,n); // NOTE: this is rho*phi; solution is phi
191 
192  // Add countergradient correction to RHS in interior
193  if (use_mrf_countergradient && (n == RhoTheta_comp || n == RhoQ1_comp)) {
194  const int gam_comp = (n == RhoTheta_comp) ? EddyDiff::HGAMT_v : EddyDiff::HGAMQ_v;
195  const Real gam_k = mu_turb(i, j, k, gam_comp);
196  const Real gam_km1 = mu_turb(i, j, k-1, gam_comp);
197  const Real gam_kp1 = mu_turb(i, j, k+1, gam_comp);
198  const Real gam_hi = myhalf * (gam_k + gam_kp1); // at k+½
199  const Real gam_lo = myhalf * (gam_k + gam_km1); // at k-½
200  // Countergradient flux divergence (implicit contribution to RHS):
201  // -Fact * dz_inv * [ρα_{k+½}·γ_{k+½} - ρα_{k-½}·γ_{k-½}]
202  // ρα·γ is already a flux, so differencing it across the cell is
203  // scaled by the *cell* spacing dz_inv. The face spacings
204  // dz_inv_hi/dz_inv_lo belong to the gradient in the diffusion
205  // coefficients above, not here; using them would leave a spurious
206  // tendency for a constant countergradient flux on a stretched grid.
207  RHS_a(i,j,k) -= Fact * dz_inv * (rhoAlpha_hi * gam_hi - rhoAlpha_lo * gam_lo);
208  }
209 
210  RHS_a(i,j,k) = (RHS_a(i,j,k) - a_tmp * RHS_a(i,j,k-1)) * inv_b2_tmp; // NOTE: This is now "rho"
211  coeffG_a(i,j,k) = c_tmp * inv_b2_tmp; // NOTE: this is now "gamma"
212  } // k
213 
214  // Top boundary coefficients and RHS for L decomp
215  //===================================================
216  {
217  getRhoAlpha(i, j, khi, rhoAlpha_lo, rhoAlpha_hi,
218  cell_data, mu_turb, d_alpha_eff, d_eddy_diff_idz,
219  prim_index, prim_scal_index, l_consA, l_turb);
220 
221  dz_inv = one / dz_ptr[khi];
222  dz_inv_lo = two / (dz_ptr[khi] + dz_ptr[khi-1]);
223  dz_inv_hi = dz_inv;
224 
225  a_tmp = -Fact * rhoAlpha_lo * dz_inv_lo * dz_inv;
226  c_tmp = zero;
227  if (!at_zhi) {
228  dz_inv_hi = two / (dz_ptr[khi] + dz_ptr[khi+1]);
229  c_tmp = -Fact * rhoAlpha_hi * dz_inv_hi * dz_inv;
230  }
231  b_tmp = cell_data(i,j,khi,Rho_comp) - a_tmp - c_tmp;
232  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,khi-1));
233 
234  RHS_a(i,j,khi) = cell_data(i,j,khi,n); // NOTE: this is rho*phi; solution is phi
235  if (!at_zhi) {
236  // Coarse/fine boundary: close the system with the known value above. The
237  // countergradient flux then passes through both faces, as it does in the
238  // interior, rather than only through the lower one.
239  RHS_a(i,j,khi) -= c_tmp * (cell_data(i,j,khi+1,n) / cell_data(i,j,khi+1,Rho_comp));
240  if (use_mrf_countergradient && (n == RhoTheta_comp || n == RhoQ1_comp)) {
241  const int gam_comp = (n == RhoTheta_comp) ? EddyDiff::HGAMT_v : EddyDiff::HGAMQ_v;
242  const Real gam_hi = myhalf * (mu_turb(i, j, khi, gam_comp) + mu_turb(i, j, khi+1, gam_comp));
243  const Real gam_lo = myhalf * (mu_turb(i, j, khi, gam_comp) + mu_turb(i, j, khi-1, gam_comp));
244  RHS_a(i,j,khi) -= Fact * dz_inv * (rhoAlpha_hi * gam_hi - rhoAlpha_lo * gam_lo);
245  }
246  } else if (neumann_on_zhi) {
247  RHS_a(i,j,khi) -= -Fact * dz_inv * rhoAlpha_hi * bc_neumann_vals[5]; // NOTE: N_val = d_z(\phi)
248  }
249 
250  // First solve
251  soln_a(i,j,khi) = (RHS_a(i,j,khi) - a_tmp * RHS_a(i,j,khi-1)) * inv_b2_tmp;
252  }
253 
254  // Back sweep the U decomp solution
255  //===================================================
256  for (int k(khi-1); k>=klo; --k) {
257  soln_a(i,j,k) = RHS_a(i,j,k) - coeffG_a(i,j,k) * soln_a(i,j,k+1);
258  }
259 
260  // Convert back to rho*theta
261  //===================================================
262  for (int k(klo); k<=khi; ++k) {
263  cell_data(i,j,k,n) = cell_data(i,j,k,Rho_comp) * soln_a(i,j,k);
264  }
265 
266 #ifdef AMREX_USE_GPU
267  });
268 #else
269  } // i
270  } // j
271 #endif
272 }
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE void getRhoAlpha(int i, int j, int k, amrex::Real &rhoAlpha_lo, amrex::Real &rhoAlpha_hi, const amrex::Array4< const amrex::Real > &cell_data, const amrex::Array4< const amrex::Real > &mu_turb, const amrex::Real *d_alpha_eff, const int *d_eddy_diff_idz, int prim_index, int prim_scal_index, bool l_consA, bool l_turb)
Definition: ERF_GetRhoAlpha.H:20
#define RhoScalar_comp
Definition: ERF_IndexDefines.H:43
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:40
#define NSCALARS
Definition: ERF_IndexDefines.H:16
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:45
#define PrimScalar_comp
Definition: ERF_IndexDefines.H:60
#define RhoKE_comp
Definition: ERF_IndexDefines.H:41
@ neumann
Definition: ERF_IndexDefines.H:302
@ HGAMQ_v
Definition: ERF_IndexDefines.H:261
@ HGAMT_v
Definition: ERF_IndexDefines.H:260
Here is the call graph for this function: