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

Macros

#define INSTANTIATE_IMPLICIT_DIFF_FOR_MOM_LU(STAGDIR)
 

Functions

void ImplicitDiffForStateLU_T (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 Array4< const Real > &z_nd, const Array4< const Real > &detJ, const GpuArray< Real, AMREX_SPACEDIM > &cellSizeInv, 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, const Array4< const Real > &mf_mx, const Array4< const Real > &mf_my, const bool implicit_metric)
 
template<int stagdir>
void ImplicitDiffForMomLU_T (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 Array4< const Real > &z_nd, const Array4< const Real > &detJ, const GpuArray< Real, AMREX_SPACEDIM > &cellSizeInv, 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, const Array4< const Real > &mf_x, const Array4< const Real > &mf_y, const bool implicit_metric)
 

Macro Definition Documentation

◆ INSTANTIATE_IMPLICIT_DIFF_FOR_MOM_LU

#define INSTANTIATE_IMPLICIT_DIFF_FOR_MOM_LU (   STAGDIR)
Value:
template void ImplicitDiffForMomLU_T<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 Array4<const Real>&, \
const Array4<const Real>&, \
const GpuArray<Real, AMREX_SPACEDIM>&, \
const Array4<const Real>&, \
const SolverChoice&, \
const BCRec*, \
const bool, \
const Real, \
const bool, \
const Array4<const Real>&, \
const Array4<const Real>&, \
const bool);
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Definition: ERF_DataStruct.H:685

Function Documentation

◆ ImplicitDiffForMomLU_T()

template<int stagdir>
void ImplicitDiffForMomLU_T ( 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 Array4< const Real > &  z_nd,
const Array4< const Real > &  detJ,
const GpuArray< Real, AMREX_SPACEDIM > &  cellSizeInv,
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,
const Array4< const Real > &  mf_x,
const Array4< const Real > &  mf_y,
const bool  implicit_metric 
)

Function for computing the implicit contribution to the vertical diffusion of momentum, over terrain.

This function (explicitly instantiated below) handles staggering in x, y, or z through the template parameter, stagdir. NOTE: implicit diffusion of w has remains an experimental feature and has not been tested yet with terrain.

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]z_ndnodal array of z
[in]detJJacobian determinant
[in]cellSizeInvinverse cell size array
[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
[in]mf_xx map factor at the faces of this component (u: mf_ux, v: mf_vx)
[in]mf_yy map factor at the faces of this component (u: mf_uy, v: mf_vy)
[in]implicit_metricerf.implicit_terrain_metric: add the compact terrain-metric coefficient K_h*M on the interior faces (u and v only)
481 {
482  BL_PROFILE_VAR("ImplicitDiffForMom_T()",ImplicitDiffForMom_T);
483 
484  Real dt = static_cast<Real>(dt_d);
485 
486  // setup quantities for getRhoAlphaAtFaces()
487  DiffChoice dc = solverChoice.diffChoice;
488  TurbChoice tc = solverChoice.turbChoice[level];
489  bool l_consA = (dc.molec_diff_type == MolecDiffType::ConstantAlpha);
490  bool l_turb = tc.use_kturb;
491  // The off-diagonal correction strains for u/v contain a factor of 1/2,
492  // while the diagonal correction strain for w does not.
493  constexpr Real molec_fac = (stagdir == 2) ? two : one;
494  Real mu_eff = (l_consA) ? molec_fac * dc.dynamic_viscosity / dc.rho0_trans
495  : molec_fac * dc.dynamic_viscosity;
496 
497  // g(S*) coefficient
498  // stagdir==0: tau_corr = myhalf * du/dz * mu_tot
499  // stagdir==1: tau_corr = myhalf * dv/dz * mu_tot
500  // stagdir==2: tau_corr = dw/dz * mu_tot
501  constexpr Real gfac = (stagdir == 2) ? two/three : one;
502 
503  // offsets used to average to faces
504  constexpr int ioff = (stagdir == 0) ? 1 : 0;
505  constexpr int joff = (stagdir == 1) ? 1 : 0;
506 
507  // Box bounds
508  int ilo = bx.smallEnd(0);
509  int ihi = bx.bigEnd(0);
510  int jlo = bx.smallEnd(1);
511  int jhi = bx.bigEnd(1);
512  int box_klo = bx.smallEnd(2);
513  int box_khi = bx.bigEnd(2);
514  amrex::ignore_unused(ilo, ihi, jlo, jhi);
515 
516  // Vertical staggering of this component, and the vertical extent of the domain for the
517  // faces we are solving on. A column whose top or bottom is not the domain boundary ends
518  // at a coarse/fine boundary instead, and must not be given a physical BC row.
519  constexpr int knodal = (stagdir == 2) ? 1 : 0;
520  const int dom_klo = domain.smallEnd(2);
521  const int dom_khi = domain.bigEnd(2) + knodal;
522 
523  // Temporary FABs for tridiagonal solve (allocated on column)
524  // A[k] * x[k-1] + B[k] * x[k] + C[k+1] = RHS[k]
525  amrex::FArrayBox RHS_fab, soln_fab, coeffG_fab;
526  RHS_fab.resize(bx,1, amrex::The_Async_Arena());
527  soln_fab.resize(bx,1, amrex::The_Async_Arena());
528  coeffG_fab.resize(bx,1, amrex::The_Async_Arena());
529  auto const& RHS_a = RHS_fab.array();
530  auto const& soln_a = soln_fab.array();
531  auto const& coeffG_a = coeffG_fab.array();
532 
533  Real dz_inv = cellSizeInv[2];
534 
535  int bc_comp = BCVars::xvel_bc + stagdir;
536  bool ext_dir_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::ext_dir ||
537  bc_ptr[bc_comp].lo(2) == ERFBCType::ext_dir_prim);
538  bool ext_dir_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::ext_dir ||
539  bc_ptr[bc_comp].hi(2) == ERFBCType::ext_dir_prim);
540  bool foextrap_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::foextrap);
541  bool foextrap_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::foextrap);
542  amrex::ignore_unused(foextrap_on_zlo,foextrap_on_zhi);
543 
544  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zlo || ext_dir_on_zlo || use_SurfLayer,
545  "Unexpected lower BC for momentum used with implicit vertical diffusion");
546  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zhi || ext_dir_on_zhi,
547  "Unexpected upper BC for momentum used with implicit vertical diffusion");
548 
549  Real Fact = implicit_fac * dt * dz_inv;
550 
551 #ifdef AMREX_USE_GPU
552  ParallelFor(makeSlab(bx,2,0), [=] AMREX_GPU_DEVICE (int i, int j, int)
553  {
554 #else
555  for (int j(jlo); j<=jhi; ++j) {
556  for (int i(ilo); i<=ihi; ++i) {
557 #endif
558 
559  // The face column at (i,j) is shared by the two cell columns on either side of it,
560  // and so by the box on either side of a grid seam. Solve only over the vertical
561  // range that both of those cell columns cover: the boxes on the two sides then
562  // build the same tridiagonal system and get the same answer, so the duplicated
563  // faces along the seam stay consistent. Faces outside that range sit on a lateral
564  // coarse/fine boundary and are set from the coarse level, so it is consistent to
565  // leave them to the explicit update.
566  const int klo = amrex::max(box_klo, col_kext(i-ioff,j-joff,0,0),
567  col_kext(i ,j ,0,0));
568  const int khi = amrex::min(box_khi, col_kext(i-ioff,j-joff,0,1)+knodal,
569  col_kext(i ,j ,0,1)+knodal);
570 
571  // Fewer than two faces in common -- nothing to invert, leave the column explicit
572 #ifdef AMREX_USE_GPU
573  if (khi <= klo) { return; }
574 #else
575  if (khi <= klo) { continue; }
576 #endif
577  // Notes:
578  //
579  // - In DiffusionSrcForMom (e.g., for x-mom)
580  //
581  // Real diffContrib = ...
582  // + (tau13(i,j,k+1) - tau13(i,j,k)) / dzinv
583  // rho_u_rhs(i,j,k) -= diffContrib; // note the negative sign
584  //
585  // - We need to scale the explicit _part_ of `tau13` (for x-mom) by (1 - implicit_fac)
586  // The part that needs to be scaled is stored in `tau_corr`.
587  // E.g., tau13 = myhalf * (du/dz + dw/dx)
588  // tau13_corr = myhalf * du/dz
589  //
590  // - The momentum (`face_data`) was set to `S_old + S_rhs * dt`
591  // prior to including "ERF_Implicit.H". Recall that S_rhs includes
592  // sources from advection and other forcings, not just diffusion.
593  //
594  // - To correct momentum, we need to subtract `implicit_fac * diffContrib_corr`
595  // from S_rhs to recover `(1 - implicit_fac) * diffContrib_corr`,
596  // where `diffContrib_corr = -d(tau_corr)/dz`. The negative sign
597  // comes from our convention for the RHS diffusion source.
598  //
599  // Subtracting a negative gives the += below; multiply by dt to
600  // get the intermediate momentum on the RHS of the tridiagonal
601  // system.
602  //
603  // - With a surface_layer BC, tau13/23 holds the vertical flux -d_z(k*u_i)
604  // directly. We must use tau at klo (not tau_corr) with SL BCs.
605  //
606  // - The detJ for divergence was multiplied through.
607  // Therefore, it doesn't show up in the A/B denominator,
608  // but it does modify B and the RHS.
609  //
610  // - Finally, the terms ~ RHS += (tau_corr_hi - tau_corr_lo) / dz (below)
611  // essentially undo the explicit diffusion update that will be
612  // handled here implicitly.
613 
614  // Bottom boundary coefficients and RHS for L decomp
615  //===================================================
616  Real rhoface, rhoAlpha_lo, rhoAlpha_hi;
617  Real detJface, met_h_zeta_lo, met_h_zeta_hi;
618  Real a_tmp, b_tmp, c_tmp, inv_b2_tmp;
619  {
620  detJface = myhalf * (detJ(i,j,klo) + detJ(i-ioff,j-joff,klo));
621  rhoface = myhalf * (cell_data(i,j,klo,Rho_comp) + cell_data(i-ioff,j-joff,klo,Rho_comp));
622  getRhoAlphaForFaces(i, j, klo, ioff, joff, rhoAlpha_lo, rhoAlpha_hi,
623  cell_data, mu_turb, mu_eff,
624  l_consA, l_turb);
625  Real metric_lo = zero, metric_hi = zero;
626  if (implicit_metric) {
627  TerrainMetricMomCoeffs<stagdir>(i, j, klo, dom_klo, dom_khi, metric_lo, metric_hi,
628  cell_data, mu_turb, mu_eff, l_consA, l_turb,
629  z_nd, cellSizeInv, mf_x, mf_y);
630  }
631 
632  met_h_zeta_lo = MomFaceHZeta<stagdir>(i,j,klo,ioff,joff,cellSizeInv,z_nd);
633  met_h_zeta_hi = MomFaceHZeta<stagdir>(i,j,klo+1,ioff,joff,cellSizeInv,z_nd);
634 
635  a_tmp = zero;
636  c_tmp = -Fact * gfac * (rhoAlpha_hi + metric_hi) * dz_inv / met_h_zeta_hi;
637 
638  RHS_a(i,j,klo) = detJface * face_data(i,j,klo); // NOTE: this is momenta; solution is velocity
639 
640  // BCs: Dirichlet (u_i = val), slip wall (w = 0), or surface layer (w = 0) --
641  // but only where the bottom of this column really is the domain boundary
642  if (klo != dom_klo) {
643  // Coarse/fine (or grid) boundary: close the system with the known face value
644  // below instead of imposing a physical BC at an interior height
645  a_tmp = -Fact * (rhoAlpha_lo + metric_lo) * dz_inv / met_h_zeta_lo;
646  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau_corr(i,j,klo));
647  const Real rho_below = myhalf * ( cell_data(i ,j ,klo-1,Rho_comp)
648  + cell_data(i-ioff,j-joff,klo-1,Rho_comp) );
649  RHS_a(i,j,klo) -= a_tmp * (face_data(i,j,klo-1) / rho_below);
650  } else if (ext_dir_on_zlo) {
651  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau_corr(i,j,klo));
652  if (stagdir==2) {
653  c_tmp = zero;
654  RHS_a(i,j,klo) = zero;
655  } else {
656  // NOTE: wall is 1/2 dz away (2 dz_inv)
657  a_tmp = -two * Fact * rhoAlpha_lo * dz_inv / met_h_zeta_lo;
658  const Real rho_wall = myhalf * ( cell_data(i ,j ,klo-1,Rho_comp)
659  + cell_data(i-ioff,j-joff,klo-1,Rho_comp) );
660  const Real wall_velocity = face_data(i,j,klo-1) / rho_wall;
661  RHS_a(i,j,klo) -= a_tmp * wall_velocity;
662  }
663  } else if (use_SurfLayer) {
664  // NOTE: tau = -mu*d_z(u_i) w/ SL
665  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau(i,j,klo));
666  RHS_a(i,j,klo) += Fact * tau(i,j,klo);
667  } else {
668  // NOTE: FOEXTRAP has zero lower flux (nothing to add to RHS)
669  RHS_a(i,j,klo) += Fact * gfac * (tau_corr(i,j,klo+1) - tau_corr(i,j,klo));
670  }
671 
672  // Add YSU momentum countergradient correction at bottom boundary.
673  // NOTE: As for the scalars, the lower face at klo carries no
674  // countergradient flux -- the surface stress is supplied by the
675  // surface layer model or the wall BC above. Only the upper face
676  // contributes here.
677  // NOTE: The sign matches the scalar path: tau_i3 = -rho*K*(du_i/dz - gamma_i),
678  // so the countergradient piece of the flux is +rho*K*gamma_i and its
679  // divergence enters the RHS with a minus sign.
680  if (use_ysu_mom_countergradient && stagdir < 2) {
681  const int hgam_comp = (stagdir == 0) ? EddyDiff::HGAMU_v : EddyDiff::HGAMV_v;
682  // Average HGAM* to the staggered face
683  const Real gam_klo = myhalf * (mu_turb(i,j,klo ,hgam_comp) + mu_turb(i-ioff,j-joff,klo ,hgam_comp));
684  const Real gam_kp1 = myhalf * (mu_turb(i,j,klo+1,hgam_comp) + mu_turb(i-ioff,j-joff,klo+1,hgam_comp));
685  const Real gam_hi = myhalf * (gam_klo + gam_kp1);
686  RHS_a(i,j,klo) -= Fact * rhoAlpha_hi * gam_hi;
687  }
688 
689  b_tmp = detJface * rhoface - a_tmp - c_tmp;
690  inv_b2_tmp = one;
691 
692  RHS_a(i,j,klo) /= b_tmp; // NOTE: this is now "rho"
693  coeffG_a(i,j,klo) = c_tmp / b_tmp; // NOTE: this is now "gamma"
694  }
695 
696  // Build the coefficients and RHS for L decomp
697  //===================================================
698  for (int k(klo+1); k < khi; k++) {
699  detJface = myhalf * (detJ(i,j,k) + detJ(i-ioff,j-joff,k));
700  rhoface = myhalf * (cell_data(i,j,k,Rho_comp) + cell_data(i-ioff,j-joff,k,Rho_comp));
701  getRhoAlphaForFaces(i, j, k, ioff, joff, rhoAlpha_lo, rhoAlpha_hi,
702  cell_data, mu_turb, mu_eff,
703  l_consA, l_turb);
704  Real metric_lo = zero, metric_hi = zero;
705  if (implicit_metric) {
706  TerrainMetricMomCoeffs<stagdir>(i, j, k, dom_klo, dom_khi, metric_lo, metric_hi,
707  cell_data, mu_turb, mu_eff, l_consA, l_turb,
708  z_nd, cellSizeInv, mf_x, mf_y);
709  }
710 
711  met_h_zeta_lo = MomFaceHZeta<stagdir>(i,j,k,ioff,joff,cellSizeInv,z_nd);
712  met_h_zeta_hi = MomFaceHZeta<stagdir>(i,j,k+1,ioff,joff,cellSizeInv,z_nd);
713 
714  a_tmp = -Fact * (rhoAlpha_lo + metric_lo) * dz_inv / met_h_zeta_lo;
715  c_tmp = -Fact * (rhoAlpha_hi + metric_hi) * dz_inv / met_h_zeta_hi;
716  b_tmp = detJface * rhoface - a_tmp - c_tmp;
717  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,k-1));
718 
719  RHS_a(i,j,k) = detJface * face_data(i,j,k); // NOTE: this is momenta; solution is velocity
720  RHS_a(i,j,k) += Fact * gfac * (tau_corr(i,j,k+1) - tau_corr(i,j,k));
721 
722  // Add YSU momentum countergradient correction
723  if (use_ysu_mom_countergradient && stagdir < 2) {
724  const int hgam_comp = (stagdir == 0) ? EddyDiff::HGAMU_v : EddyDiff::HGAMV_v;
725  // Average HGAM* to the staggered face
726  const Real gam_k = myhalf * (mu_turb(i,j,k ,hgam_comp) + mu_turb(i-ioff,j-joff,k ,hgam_comp));
727  const Real gam_km1 = myhalf * (mu_turb(i,j,k-1,hgam_comp) + mu_turb(i-ioff,j-joff,k-1,hgam_comp));
728  const Real gam_kp1 = myhalf * (mu_turb(i,j,k+1,hgam_comp) + mu_turb(i-ioff,j-joff,k+1,hgam_comp));
729  const Real gam_hi = myhalf * (gam_k + gam_kp1); // at k+1/2
730  const Real gam_lo = myhalf * (gam_k + gam_km1); // at k-1/2
731  RHS_a(i,j,k) -= Fact * (rhoAlpha_hi * gam_hi - rhoAlpha_lo * gam_lo);
732  }
733 
734  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"
735  coeffG_a(i,j,k) = c_tmp * inv_b2_tmp; // NOTE: this is now "gamma"
736  } // k
737 
738  // Top boundary coefficients and RHS for L decomp
739  //===================================================
740  {
741  detJface = myhalf * (detJ(i,j,khi) + detJ(i-ioff,j-joff,khi));
742  rhoface = myhalf * (cell_data(i,j,khi,Rho_comp) + cell_data(i-ioff,j-joff,khi,Rho_comp));
743  getRhoAlphaForFaces(i, j, khi, ioff, joff, rhoAlpha_lo, rhoAlpha_hi,
744  cell_data, mu_turb, mu_eff,
745  l_consA, l_turb);
746  Real metric_lo = zero, metric_hi = zero;
747  if (implicit_metric) {
748  TerrainMetricMomCoeffs<stagdir>(i, j, khi, dom_klo, dom_khi, metric_lo, metric_hi,
749  cell_data, mu_turb, mu_eff, l_consA, l_turb,
750  z_nd, cellSizeInv, mf_x, mf_y);
751  }
752 
753  met_h_zeta_lo = MomFaceHZeta<stagdir>(i,j,khi,ioff,joff,cellSizeInv,z_nd);
754  met_h_zeta_hi = MomFaceHZeta<stagdir>(i,j,khi+1,ioff,joff,cellSizeInv,z_nd);
755 
756  a_tmp = -Fact * gfac * (rhoAlpha_lo + metric_lo) * dz_inv / met_h_zeta_lo;
757  c_tmp = zero;
758 
759  RHS_a(i,j,khi) = detJface * face_data(i,j,khi); // NOTE: this is momenta; solution is velocity
760  RHS_a(i,j,khi) += Fact * gfac * (tau_corr(i,j,khi+1) - tau_corr(i,j,khi));
761 
762  // BCs: Dirichlet (u_i = val), slip wall (w = 0) --
763  // but only where the top of this column really is the domain boundary
764  if (khi != dom_khi) {
765  // Coarse/fine (or grid) boundary: close the system with the known face value
766  // above instead of imposing a physical BC at an interior height
767  c_tmp = -Fact * (rhoAlpha_hi + metric_hi) * dz_inv / met_h_zeta_hi;
768  const Real rho_above = myhalf * ( cell_data(i ,j ,khi+1,Rho_comp)
769  + cell_data(i-ioff,j-joff,khi+1,Rho_comp) );
770  RHS_a(i,j,khi) -= c_tmp * (face_data(i,j,khi+1) / rho_above);
771  } else if (ext_dir_on_zhi) {
772  if (stagdir==2) {
773  a_tmp = zero;
774  RHS_a(i,j,khi) = zero;
775  } else {
776  // NOTE: wall is 1/2 dz away (2 dz_inv)
777  c_tmp = -two * Fact * rhoAlpha_hi * dz_inv / met_h_zeta_hi;
778  const Real rho_wall = myhalf * ( cell_data(i ,j ,khi+1,Rho_comp)
779  + cell_data(i-ioff,j-joff,khi+1,Rho_comp) );
780  const Real wall_velocity = face_data(i,j,khi+1) / rho_wall;
781  RHS_a(i,j,khi) -= c_tmp * wall_velocity;
782  }
783  }
784 
785  b_tmp = detJface * rhoface - a_tmp - c_tmp;
786  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,khi-1));
787 
788  // First solve
789  soln_a(i,j,khi) = (RHS_a(i,j,khi) - a_tmp * RHS_a(i,j,khi-1)) * inv_b2_tmp;
790  }
791 
792  // Back sweep the U decomp solution
793  //===================================================
794  for (int k(khi-1); k>=klo; --k) {
795  soln_a(i,j,k) = RHS_a(i,j,k) - coeffG_a(i,j,k) * soln_a(i,j,k+1);
796  }
797 
798  // Convert back to momenta
799  //===================================================
800  for (int k(klo); k<=khi; ++k) {
801  rhoface = myhalf * (cell_data(i,j,k,Rho_comp) + cell_data(i-ioff,j-joff,k,Rho_comp));
802  face_data(i,j,k) = rhoface * soln_a(i,j,k);
803  }
804 
805 #ifdef AMREX_USE_GPU
806  });
807 #else
808  } // i
809  } // j
810 #endif
811 }
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, const int eddy_comp=EddyDiff::Mom_v)
Definition: ERF_GetRhoAlphaForFaces.H:24
#define Rho_comp
Definition: ERF_IndexDefines.H:42
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:39
constexpr amrex::Real two
Definition: ERF_NumericalConstants.H:38
constexpr amrex::Real one
Definition: ERF_NumericalConstants.H:37
constexpr amrex::Real zero
Definition: ERF_NumericalConstants.H:36
constexpr amrex::Real myhalf
Definition: ERF_NumericalConstants.H:41
AMREX_ASSERT_WITH_MESSAGE(wbar_cutoff_min > wbar_cutoff_max, "ERROR: wbar_cutoff_min < wbar_cutoff_max")
@ xvel_bc
Definition: ERF_IndexDefines.H:108
@ foextrap
Definition: ERF_IndexDefines.H:299
@ ext_dir
Definition: ERF_IndexDefines.H:300
@ ext_dir_prim
Definition: ERF_IndexDefines.H:303
@ HGAMU_v
Definition: ERF_IndexDefines.H:265
@ HGAMV_v
Definition: ERF_IndexDefines.H:266
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:2252
DiffChoice diffChoice
Diffusion-related options.
Definition: ERF_DataStruct.H:2249
Definition: ERF_TurbStruct.H:115
bool use_kturb
Whether any turbulence model is active.
Definition: ERF_TurbStruct.H:921
Here is the call graph for this function:

◆ ImplicitDiffForStateLU_T()

void ImplicitDiffForStateLU_T ( 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 Array4< const Real > &  z_nd,
const Array4< const Real > &  detJ,
const GpuArray< Real, AMREX_SPACEDIM > &  cellSizeInv,
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,
const Array4< const Real > &  mf_mx,
const Array4< const Real > &  mf_my,
const bool  implicit_metric 
)

Function for computing the implicit contribution to the vertical diffusion of theta, with 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]z_ndnodal array of z
[in]detJJacobian determinant
[in]cellSizeInvinverse cell size array
[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
[in]mf_mxx map factor at cell centers
[in]mf_myy map factor at cell centers
[in]implicit_metricerf.implicit_terrain_metric: add the compact terrain-metric coefficient K_h*M_s on the interior faces
98 {
99  BL_PROFILE_VAR("ImplicitDiffForState_T()",ImplicitDiffForState_T);
100 
101  Real dt = static_cast<Real>(dt_d);
102 
103  // setup quantities for getRhoAlpha()
104 #include "ERF_SetupVertDiff.H"
105  const int qty_index = n;
106  const int prim_index = qty_index - 1;
107  const int prim_scal_index = (qty_index >= RhoScalar_comp && qty_index < RhoScalar_comp+NSCALARS) ? PrimScalar_comp : prim_index;
108 
109  // erf.implicit_terrain_metric: the horizontal eddy diffusivity of this quantity, from the
110  // selector the explicit fluxes use, so that the two sides cannot drift apart
111  const int eddy_h_comp =
113  const int dom_klo = domain.smallEnd(2);
114  const int dom_khi = domain.bigEnd(2);
115 
116  // Box bounds
117  int ilo = bx.smallEnd(0);
118  int ihi = bx.bigEnd(0);
119  int jlo = bx.smallEnd(1);
120  int jhi = bx.bigEnd(1);
121  int klo = bx.smallEnd(2);
122  int khi = bx.bigEnd(2);
123  amrex::ignore_unused(ilo, ihi, jlo, jhi);
124 
125  // Temporary FABs for tridiagonal solve (allocated on column)
126  // A[k] * x[k-1] + B[k] * x[k] + C[k+1] = RHS[k]
127 
128  // With LU decomposition, M * x = r is written as L * U * x = r with U * x = rho
129  // We then first have L * rho = r and U * x = rho
130  amrex::FArrayBox RHS_fab, soln_fab, coeffG_fab;
131  RHS_fab.resize(bx,1, amrex::The_Async_Arena());
132  soln_fab.resize(bx,1, amrex::The_Async_Arena());
133  coeffG_fab.resize(bx,1, amrex::The_Async_Arena());
134  auto const& RHS_a = RHS_fab.array();
135  auto const& soln_a = soln_fab.array();
136  auto const& coeffG_a = coeffG_fab.array();
137 
138  Real dz_inv = cellSizeInv[2];
139 
140  int bc_comp = qty_index;
141  bool foextrap_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::foextrap);
142  bool foextrap_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::foextrap);
143  bool neumann_on_zlo = (bc_ptr[bc_comp].lo(2) == ERFBCType::neumann);
144  bool neumann_on_zhi = (bc_ptr[bc_comp].hi(2) == ERFBCType::neumann);
145  amrex::ignore_unused(foextrap_on_zlo, foextrap_on_zhi);
146 
147  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zlo || neumann_on_zlo || use_SurfLayer,
148  "Unexpected lower BC for scalars used with implicit vertical diffusion");
149  AMREX_ASSERT_WITH_MESSAGE(foextrap_on_zhi || neumann_on_zhi,
150  "Unexpected upper BC for scalars used with implicit vertical diffusion");
151 
152  // k-eqn RANS with a Dirichlet wall value of k: the first cell is held at
153  // the value set by the surface layer, so its row reduces to x(klo) = phi(klo)
154  // and the row above sees it as a Dirichlet neighbour.
155  // NOTE: init_bcs requires zlo.type = surface_layer whenever dirichlet_k is set,
156  // so this matches the l_dirichlet_k pin in erf_slow_rhs_post, which also
157  // requires a surface layer. Do not relax one without the other.
158  const bool pin_klo = (qty_index == RhoKE_comp) && (klo == domain.smallEnd(2)) &&
159  (solverChoice.turbChoice[level].rans_type == RANSType::kEqn) &&
160  solverChoice.turbChoice[level].dirichlet_k;
161 
162  // A column whose bottom or top is not the domain boundary ends at a coarse/fine boundary
163  // instead. There the system must be closed with the value on the other side of that
164  // boundary rather than given a physical BC row at an interior height, so that the answer
165  // does not depend on where the grid happens to end in the vertical.
166  const bool at_zlo = (klo == domain.smallEnd(2));
167  const bool at_zhi = (khi == domain.bigEnd(2));
168 
169  Real Fact = implicit_fac * dt * dz_inv;
170 
171 #ifdef AMREX_USE_GPU
172  ParallelFor(makeSlab(bx,2,0), [=] AMREX_GPU_DEVICE (int i, int j, int)
173  {
174 #else
175  for (int j(jlo); j<=jhi; ++j) {
176  for (int i(ilo); i<=ihi; ++i) {
177 #endif
178  // Notes: The detJ for divergence was multiplied through.
179  // Therefore, it doesn't show up in the A/B denominator,
180  // but it does modify B and the RHS.
181 
182  // Bottom boundary coefficients and RHS for L decomp
183  //===================================================
184  Real rhoAlpha_lo, rhoAlpha_hi;
185  Real met_h_zeta_lo, met_h_zeta_hi;
186  Real a_tmp, b_tmp, c_tmp, inv_b2_tmp;
187  {
188  getRhoAlpha(i, j, klo, rhoAlpha_lo, rhoAlpha_hi,
189  cell_data, mu_turb, d_alpha_eff, d_eddy_diff_idz,
190  prim_index, prim_scal_index, l_consA, l_turb);
191  Real metric_lo = zero, metric_hi = zero;
192  if (implicit_metric) {
193  const Real alpha_mol = (l_consA && l_turb) ? d_alpha_eff[prim_scal_index]
194  : d_alpha_eff[prim_index];
195  TerrainMetricScalarCoeffs(i, j, klo, dom_klo, dom_khi, metric_lo, metric_hi,
196  cell_data, mu_turb, alpha_mol, eddy_h_comp,
197  l_consA, l_turb, z_nd, cellSizeInv, mf_mx, mf_my);
198  }
199 
200  met_h_zeta_hi = Compute_h_zeta_AtKface(i,j,klo+1,cellSizeInv,z_nd);
201 
202  a_tmp = zero;
203  if (!at_zlo) {
204  met_h_zeta_lo = Compute_h_zeta_AtKface(i,j,klo,cellSizeInv,z_nd);
205  a_tmp = -Fact * (rhoAlpha_lo + metric_lo) * dz_inv / met_h_zeta_lo;
206  }
207  c_tmp = (pin_klo) ? zero : -Fact * (rhoAlpha_hi + metric_hi) * dz_inv / met_h_zeta_hi;
208  b_tmp = detJ(i,j,klo) * cell_data(i,j,klo,Rho_comp) - a_tmp - c_tmp;
209  inv_b2_tmp = one;
210 
211  RHS_a(i,j,klo) = detJ(i,j,klo) * cell_data(i,j,klo,n); // NOTE: this is rho*phi; solution is phi
212  if (!at_zlo) {
213  // Coarse/fine boundary: close the system with the known value below
214  RHS_a(i,j,klo) -= a_tmp * (cell_data(i,j,klo-1,n) / cell_data(i,j,klo-1,Rho_comp));
215  } else if (pin_klo) {
216  // Dirichlet row: no flux terms
217  } else if (use_SurfLayer && scalar_zflux) {
218  RHS_a(i,j,klo) += Fact * scalar_zflux(i,j,klo); // NOTE: scalar_zflux = -K*d_z(\phi)
219  } else if (neumann_on_zlo) {
220  RHS_a(i,j,klo) += -Fact * rhoAlpha_lo * bc_neumann_vals[2]; // NOTE: N_val = d_z(\phi)
221  }
222 
223  // Add countergradient correction to RHS at bottom boundary.
224  // NOTE: The lower face at klo carries no countergradient flux -- the
225  // total surface flux is supplied by the surface layer model or the
226  // Neumann BC, while gamma represents nonlocal transport interior to
227  // the PBL. Only the upper face contributes here.
228  if (use_mrf_countergradient && (n == RhoTheta_comp || n == RhoQ1_comp)) {
229  const int gam_comp = (n == RhoTheta_comp) ? EddyDiff::HGAMT_v : EddyDiff::HGAMQ_v;
230  const Real gam_hi = myhalf * (mu_turb(i, j, klo, gam_comp) + mu_turb(i, j, klo+1, gam_comp));
231  RHS_a(i,j,klo) -= Fact * rhoAlpha_hi * gam_hi;
232  if (!at_zlo) {
233  const Real gam_lo = myhalf * (mu_turb(i, j, klo, gam_comp) + mu_turb(i, j, klo-1, gam_comp));
234  RHS_a(i,j,klo) += Fact * rhoAlpha_lo * gam_lo;
235  }
236  }
237 
238  RHS_a(i,j,klo) /= b_tmp; // NOTE: this is now "rho"
239  coeffG_a(i,j,klo) = c_tmp / b_tmp; // NOTE: this is now "gamma"
240  }
241 
242  // Build the coefficients and RHS for L decomp
243  //===================================================
244  for (int k(klo+1); k < khi; k++) {
245  getRhoAlpha(i, j, k, rhoAlpha_lo, rhoAlpha_hi,
246  cell_data, mu_turb, d_alpha_eff, d_eddy_diff_idz,
247  prim_index, prim_scal_index, l_consA, l_turb);
248  Real metric_lo = zero, metric_hi = zero;
249  if (implicit_metric) {
250  const Real alpha_mol = (l_consA && l_turb) ? d_alpha_eff[prim_scal_index]
251  : d_alpha_eff[prim_index];
252  TerrainMetricScalarCoeffs(i, j, k, dom_klo, dom_khi, metric_lo, metric_hi,
253  cell_data, mu_turb, alpha_mol, eddy_h_comp,
254  l_consA, l_turb, z_nd, cellSizeInv, mf_mx, mf_my);
255  }
256 
257  met_h_zeta_lo = Compute_h_zeta_AtKface(i,j,k ,cellSizeInv,z_nd);
258  met_h_zeta_hi = Compute_h_zeta_AtKface(i,j,k+1,cellSizeInv,z_nd);
259 
260  a_tmp = -Fact * (rhoAlpha_lo + metric_lo) * dz_inv / met_h_zeta_lo;
261  c_tmp = -Fact * (rhoAlpha_hi + metric_hi) * dz_inv / met_h_zeta_hi;
262  b_tmp = detJ(i,j,k) * cell_data(i,j,k,Rho_comp) - a_tmp - c_tmp;
263  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,k-1));
264 
265  RHS_a(i,j,k) = detJ(i,j,k) * cell_data(i,j,k,n); // NOTE: this is rho*phi; solution is phi
266 
267  // Add countergradient correction to RHS in interior
268  if (use_mrf_countergradient && (n == RhoTheta_comp || n == RhoQ1_comp)) {
269  const int gam_comp = (n == RhoTheta_comp) ? EddyDiff::HGAMT_v : EddyDiff::HGAMQ_v;
270  const Real gam_k = mu_turb(i, j, k, gam_comp);
271  const Real gam_km1 = mu_turb(i, j, k-1, gam_comp);
272  const Real gam_kp1 = mu_turb(i, j, k+1, gam_comp);
273  const Real gam_hi = myhalf * (gam_k + gam_kp1); // at k+½
274  const Real gam_lo = myhalf * (gam_k + gam_km1); // at k-½
275  // Countergradient flux divergence (implicit contribution to RHS):
276  // -Fact * [ρα_{k+½}·γ_{k+½} - ρα_{k-½}·γ_{k-½}]
277  // NOTE: no 1/met_h_zeta here. met_h_zeta appears in a_tmp/c_tmp only
278  // because the diffusive flux contains an inner vertical
279  // derivative d(phi)/dz ~ dz_inv/h_zeta * delta_phi. The
280  // countergradient piece contains no derivative, so it takes only
281  // the outer dz_inv already carried in Fact.
282  RHS_a(i,j,k) -= Fact * (rhoAlpha_hi * gam_hi - rhoAlpha_lo * gam_lo);
283  }
284 
285  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"
286  coeffG_a(i,j,k) = c_tmp * inv_b2_tmp; // NOTE: this is now "gamma"
287  } // k
288 
289  // Top boundary coefficients and RHS for L decomp
290  //===================================================
291  {
292  getRhoAlpha(i, j, khi, rhoAlpha_lo, rhoAlpha_hi,
293  cell_data, mu_turb, d_alpha_eff, d_eddy_diff_idz,
294  prim_index, prim_scal_index, l_consA, l_turb);
295  Real metric_lo = zero, metric_hi = zero;
296  if (implicit_metric) {
297  const Real alpha_mol = (l_consA && l_turb) ? d_alpha_eff[prim_scal_index]
298  : d_alpha_eff[prim_index];
299  TerrainMetricScalarCoeffs(i, j, khi, dom_klo, dom_khi, metric_lo, metric_hi,
300  cell_data, mu_turb, alpha_mol, eddy_h_comp,
301  l_consA, l_turb, z_nd, cellSizeInv, mf_mx, mf_my);
302  }
303 
304  // Lower-face metric shared with row khi-1.
305  met_h_zeta_lo = Compute_h_zeta_AtKface(i,j,khi ,cellSizeInv,z_nd);
306 
307  a_tmp = -Fact * (rhoAlpha_lo + metric_lo) * dz_inv / met_h_zeta_lo;
308  c_tmp = zero;
309  if (!at_zhi) {
310  met_h_zeta_hi = Compute_h_zeta_AtKface(i,j,khi+1,cellSizeInv,z_nd);
311  c_tmp = -Fact * (rhoAlpha_hi + metric_hi) * dz_inv / met_h_zeta_hi;
312  }
313  b_tmp = detJ(i,j,khi) * cell_data(i,j,khi,Rho_comp) - a_tmp - c_tmp;
314  inv_b2_tmp = one / (b_tmp - a_tmp * coeffG_a(i,j,khi-1));
315 
316  RHS_a(i,j,khi) = detJ(i,j,khi) * cell_data(i,j,khi,n); // NOTE: this is rho*phi; solution is phi
317  if (!at_zhi) {
318  // Coarse/fine boundary: close the system with the known value above. The
319  // countergradient flux then passes through both faces, as it does in the
320  // interior, rather than only through the lower one.
321  RHS_a(i,j,khi) -= c_tmp * (cell_data(i,j,khi+1,n) / cell_data(i,j,khi+1,Rho_comp));
322  if (use_mrf_countergradient && (n == RhoTheta_comp || n == RhoQ1_comp)) {
323  const int gam_comp = (n == RhoTheta_comp) ? EddyDiff::HGAMT_v : EddyDiff::HGAMQ_v;
324  const Real gam_hi = myhalf * (mu_turb(i, j, khi, gam_comp) + mu_turb(i, j, khi+1, gam_comp));
325  const Real gam_lo = myhalf * (mu_turb(i, j, khi, gam_comp) + mu_turb(i, j, khi-1, gam_comp));
326  RHS_a(i,j,khi) -= Fact * (rhoAlpha_hi * gam_hi - rhoAlpha_lo * gam_lo);
327  }
328  } else if (neumann_on_zhi) {
329  RHS_a(i,j,khi) -= -Fact * rhoAlpha_hi * bc_neumann_vals[5]; // NOTE: N_val = d_z(\phi)
330  }
331 
332  // First solve
333  soln_a(i,j,khi) = (RHS_a(i,j,khi) - a_tmp * RHS_a(i,j,khi-1)) * inv_b2_tmp;
334  }
335 
336  // Back sweep the U decomp solution
337  //===================================================
338  for (int k(khi-1); k>=klo; --k) {
339  soln_a(i,j,k) = RHS_a(i,j,k) - coeffG_a(i,j,k) * soln_a(i,j,k+1);
340  }
341 
342  // Convert back to rho*theta
343  //===================================================
344  for (int k(klo); k<=khi; ++k) {
345  cell_data(i,j,k,n) = cell_data(i,j,k,Rho_comp) * soln_a(i,j,k);
346  }
347 
348 #ifdef AMREX_USE_GPU
349  });
350 #else
351  } // i
352  } // j
353 #endif
354 }
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:46
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:43
#define NSCALARS
Definition: ERF_IndexDefines.H:16
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:48
#define PrimScalar_comp
Definition: ERF_IndexDefines.H:63
#define RhoKE_comp
Definition: ERF_IndexDefines.H:44
NativeScalarDiffusionPolicy ResolveNativeScalarDiffusionPolicy(const int qty_comp, const DiffChoice &choice)
Definition: ERF_NativeScalarDiffusion.H:25
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_h_zeta_AtKface(const int &i, const int &j, const int &k, const amrex::GpuArray< amrex::Real, AMREX_SPACEDIM > &cellSizeInv, const amrex::Array4< const amrex::Real > &z_nd)
Definition: ERF_TerrainMetrics.H:416
@ neumann
Definition: ERF_IndexDefines.H:305
@ HGAMQ_v
Definition: ERF_IndexDefines.H:264
@ HGAMT_v
Definition: ERF_IndexDefines.H:263
ScalarDiffusionCoefficients coefficients
Definition: ERF_NativeScalarDiffusion.H:12
int eddy_h_comp
Definition: ERF_ScalarDiffusion.H:25
Here is the call graph for this function: