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

Functions

void ComputeTurbulentViscosityLES (Vector< std::unique_ptr< MultiFab >> &Tau_lev, const MultiFab &cons_in, MultiFab &eddyViscosity, MultiFab &Diss, const Geometry &geom, bool use_terrain_fitted_coords, Vector< std::unique_ptr< MultiFab >> &mapfac, const std::unique_ptr< MultiFab > &z_phys_nd, const TurbChoice &turbChoice, const Real const_grav, std::unique_ptr< SurfaceLayer > &, const MoistureComponentIndices &moisture_indices, const MultiFab *xvel, const MultiFab *yvel)
 
void ComputeTurbulentViscosityLES_EB (Vector< std::unique_ptr< MultiFab >> &Tau_lev, const MultiFab &cons_in, MultiFab &eddyViscosity, MultiFab &Hfx1, MultiFab &Hfx2, MultiFab &Hfx3, const Geometry &geom, Vector< std::unique_ptr< MultiFab >> &mapfac, const TurbChoice &turbChoice, const Real const_grav, [[maybe_unused]] const SolverChoice &solverChoice, std::unique_ptr< SurfaceLayer > &, const MoistureComponentIndices &moisture_indices, const eb_ &ebfact, const MultiFab *xvel, const MultiFab *yvel)
 
void ComputeTurbulentViscosityRANS (int level, Vector< std::unique_ptr< MultiFab >> &Tau_lev, const MultiFab &cons_in, const MultiFab &wdist, MultiFab &eddyViscosity, MultiFab &Diss, const Geometry &geom, bool use_terrain_fitted_coords, const std::unique_ptr< MultiFab > &z_phys_nd, const TurbChoice &turbChoice, const Real const_grav, std::unique_ptr< SurfaceLayer > &SurfLayer, const MultiFab *z_0, const CanopyTurbulence &canopy, const MultiFab *forest_drag, const MultiFab *ib_blank, bool flat_wall_diss, Real ib_small_volfrac, Real ib_z0)
 
void ComputeTurbulentViscosity (double dt, const MultiFab &xvel, const MultiFab &yvel, Vector< std::unique_ptr< MultiFab >> &Tau_lev, MultiFab &cons_in, const MultiFab &wdist, MultiFab &eddyViscosity, MultiFab &Hfx1, MultiFab &Hfx2, MultiFab &Hfx3, MultiFab &Diss, const Geometry &geom, Vector< std::unique_ptr< MultiFab >> &mapfac, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const SolverChoice &solverChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, const MultiFab *z_0, const bool &use_terrain_fitted_coords, const bool &use_moisture, int level, const BCRec *bc_ptr, const eb_ &ebfact, bool vert_only, const MultiFab *qheating_rates, const MultiFab *terrain_blank, const MultiFab *forest_drag)
 

Function Documentation

◆ ComputeTurbulentViscosity()

void ComputeTurbulentViscosity ( double  dt,
const MultiFab &  xvel,
const MultiFab &  yvel,
Vector< std::unique_ptr< MultiFab >> &  Tau_lev,
MultiFab &  cons_in,
const MultiFab &  wdist,
MultiFab &  eddyViscosity,
MultiFab &  Hfx1,
MultiFab &  Hfx2,
MultiFab &  Hfx3,
MultiFab &  Diss,
const Geometry &  geom,
Vector< std::unique_ptr< MultiFab >> &  mapfac,
const std::unique_ptr< MultiFab > &  z_phys_nd,
const std::unique_ptr< MultiFab > &  z_phys_cc,
const SolverChoice &  solverChoice,
std::unique_ptr< SurfaceLayer > &  SurfLayer,
const MultiFab *  z_0,
const bool &  use_terrain_fitted_coords,
const bool &  use_moisture,
int  level,
const BCRec *  bc_ptr,
const eb_ &  ebfact,
bool  vert_only,
const MultiFab *  qheating_rates,
const MultiFab *  terrain_blank,
const MultiFab *  forest_drag 
)

Wrapper to compute turbulent viscosity with LES or PBL.

Parameters
[in]dttime step
[in]xvelvelocity in x-dir
[in]yvelvelocity in y-dir
[in]Tau_levstrain at this level
[in]cons_incell center conserved quantities
[in]wdistwall distance
[out]eddyViscosityturbulent viscosity
[out]Hfx1heat flux in x-dir
[out]Hfx2heat flux in y-dir
[out]Hfx3heat flux in z-dir
[out]Dissdissipation of turbulent kinetic energy
[in]geomproblem geometry
[in]mapfacmap factors
[in]z_phys_ndnodal physical z coordinates
[in]z_phys_cccell-centered physical z coordinates
[in]solverChoicecontainer with solver, diffusion, and turbulence parameters
[in]SurfLayeroptional surface-layer model
[in]z_0roughness length
[in]use_terrain_fitted_coordsflag for terrain-fitted coordinates
[in]use_moistureflag for moisture physics
[in]levelAMR level
[in]bc_ptrboundary condition records
[in]ebfactEB factories for cell- and face-centered variables
[in]vert_onlyflag for vertical components of eddyViscosity
[in]qheating_ratesradiation heating rates (SW, LW components)
[in]terrain_blankimmersed forcing mask, or null
[in]forest_dragforest drag field C_d LAD, or null without a forest
960 {
961  BL_PROFILE_VAR("ComputeTurbulentViscosity()",ComputeTurbulentViscosity);
962  //
963  // In LES mode, the turbulent viscosity is isotropic (unless mix_isotropic is set to false), so
964  // the LES model sets both horizontal and vertical viscosities
965  //
966  // In PBL mode, the primary purpose of the PBL model is to control vertical transport, so the PBL model sets the vertical viscosity.
967  // Optionally, the PBL model can be run in conjunction with an LES model that sets the horizontal viscosity
968  // (this isn’t truly LES, but the model form is the same as Smagorinsky).
969  //
970  // ComputeTurbulentViscosityLES populates the LES viscosity for both horizontal and vertical components.
971  // ComputeTurbulentViscosityPBL computes the PBL viscosity just for the vertical component.
972  //
973 
974  TurbChoice turbChoice = solverChoice.turbChoice[level];
975  const Real const_grav = solverChoice.gravity;
976 
977  if (!SurfLayer) {
978  AMREX_ALWAYS_ASSERT(!vert_only);
979  }
980 
981  bool impose_phys_bcs = true;
982 
983  if (turbChoice.les_type != LESType::None) {
984  if (solverChoice.terrain_type == TerrainType::EB) {
986  cons_in, eddyViscosity,
987  Hfx1, Hfx2, Hfx3,
988  geom,
989  mapfac, turbChoice, const_grav,
990  solverChoice,
991  SurfLayer, solverChoice.moisture_indices,
992  ebfact, &xvel, &yvel);
993  } else {
995  cons_in, eddyViscosity,
996  Diss,
997  geom, use_terrain_fitted_coords,
998  mapfac, z_phys_nd, turbChoice, const_grav,
999  SurfLayer, solverChoice.moisture_indices,
1000  &xvel, &yvel);
1001  }
1002  }
1003 
1004  if (turbChoice.rans_type != RANSType::None) {
1005  ComputeTurbulentViscosityRANS(level, Tau_lev, cons_in, wdist,
1006  eddyViscosity,
1007  Diss,
1008  geom, use_terrain_fitted_coords,
1009  z_phys_nd, turbChoice, const_grav,
1010  SurfLayer, z_0,
1011  solverChoice.canopyTurb, forest_drag,
1012  (solverChoice.if_fraction_stress) ? terrain_blank : nullptr,
1013  solverChoice.if_flat_wall_dissipation,
1014  solverChoice.if_small_volfrac,
1015  solverChoice.if_z0);
1016  }
1017 
1018  if (turbChoice.pbl_type == PBLType::MYJ) {
1019  ComputeDiffusivityMYJ(dt, xvel, yvel, cons_in, eddyViscosity,
1020  geom, turbChoice, SurfLayer,
1021  use_terrain_fitted_coords, use_moisture,
1022  level, bc_ptr, vert_only, z_phys_nd, z_phys_cc,
1023  solverChoice.moisture_indices);
1024  } else if (turbChoice.pbl_type == PBLType::MYNN25) {
1025  ComputeDiffusivityMYNN25(xvel, yvel, cons_in, eddyViscosity,
1026  geom, turbChoice, SurfLayer,
1027  use_terrain_fitted_coords, use_moisture,
1028  level, bc_ptr, vert_only, z_phys_nd, z_phys_cc,
1029  solverChoice.moisture_indices);
1030  } else if (turbChoice.pbl_type == PBLType::MYNNEDMF) {
1031  ComputeDiffusivityMYNNEDMF(xvel, yvel, cons_in, eddyViscosity,
1032  geom, turbChoice, SurfLayer,
1033  use_terrain_fitted_coords, use_moisture,
1034  level, bc_ptr, vert_only, z_phys_nd, z_phys_cc,
1035  solverChoice.moisture_indices);
1036  } else if (turbChoice.pbl_type == PBLType::YSU) {
1037  ComputeDiffusivityYSU(xvel, yvel, cons_in, eddyViscosity,
1038  geom, turbChoice, SurfLayer,
1039  use_terrain_fitted_coords, use_moisture,
1040  level, bc_ptr, vert_only, z_phys_nd, z_phys_cc,
1041  solverChoice.moisture_indices);
1042  } else if (turbChoice.pbl_type == PBLType::MRF) {
1043  ComputeDiffusivityMRF(xvel, yvel, cons_in, eddyViscosity,
1044  geom, turbChoice, SurfLayer,
1045  use_terrain_fitted_coords, use_moisture,
1046  level, bc_ptr, vert_only, z_phys_nd, z_phys_cc,
1047  solverChoice.moisture_indices, terrain_blank);
1048  } else if (turbChoice.pbl_type == PBLType::YSUNew) {
1049  ComputeDiffusivityYSUNew(xvel, yvel, cons_in, eddyViscosity,
1050  geom, turbChoice, SurfLayer,
1051  use_terrain_fitted_coords, use_moisture,
1052  level, bc_ptr, vert_only, z_phys_nd, z_phys_cc,
1053  solverChoice.moisture_indices,
1054  qheating_rates, terrain_blank);
1055  } else if (turbChoice.uses_shoc_family()) {
1056  // NOTE: Nothing to do here. The SHOC class handles setting the vertical
1057  // components of eddyDiffs in slow RHS pre.
1058  }
1059 
1060  //
1061  // At all levels we need to fill values outside the physical boundary for the LES coeffs.
1062  // In addition, for all cases, if at level > 0, we want to fill fine ghost cell values that
1063  // overlie coarse grid cells (and that are not in another fine valid region) with
1064  // extrapolated values from the interior, rather than interpolating from the coarser level,
1065  // since we may be using a different turbulence model there.
1066  //
1067  // Note: here "covered" refers to "covered by valid region of another grid at this level"
1068  // Note: here "physbnd" refers to "cells outside the domain if not periodic"
1069  // Note: here "interior" refers to "valid cells, i.e. inside 'my' grid"
1070  //
1071  {
1072  int is_covered = 0;
1073  int is_notcovered = 1;
1074  int is_physbnd = 2;
1075  int is_interior = 3;
1076  iMultiFab cc_mask(eddyViscosity.boxArray(),eddyViscosity.DistributionMap(),1,1);
1077  cc_mask.BuildMask(geom.Domain(), geom.periodicity(), is_covered, is_notcovered, is_physbnd, is_interior);
1078 
1079  Box domain = geom.Domain();
1080  for (int i = 0; i < AMREX_SPACEDIM; ++i) {
1081  if (geom.isPeriodic(i)) {
1082  domain.grow(i,1);
1083  }
1084  }
1085 
1086  eddyViscosity.FillBoundary(geom.periodicity());
1087 
1088  int ncomp = eddyViscosity.nComp();
1089 
1090 #ifdef _OPENMP
1091 #pragma omp parallel if (Gpu::notInLaunchRegion())
1092 #endif
1093  for (MFIter mfi(eddyViscosity); mfi.isValid(); ++mfi)
1094  {
1095  Box vbx = mfi.validbox();
1096 
1097  Box planex_lo = mfi.growntilebox(1); planex_lo.setBig(0, vbx.smallEnd(0)-1);
1098  Box planey_lo = mfi.growntilebox(1); planey_lo.setBig(1, vbx.smallEnd(1)-1);
1099  Box planez_lo = mfi.growntilebox(1); planez_lo.setBig(2, vbx.smallEnd(2)-1);
1100 
1101  Box planex_hi = mfi.growntilebox(1); planex_hi.setSmall(0, vbx.bigEnd(0)+1);
1102  Box planey_hi = mfi.growntilebox(1); planey_hi.setSmall(1, vbx.bigEnd(1)+1);
1103  Box planez_hi = mfi.growntilebox(1); planez_hi.setSmall(2, vbx.bigEnd(2)+1);
1104 
1105  int i_lo = vbx.smallEnd(0); int i_hi = vbx.bigEnd(0);
1106  int j_lo = vbx.smallEnd(1); int j_hi = vbx.bigEnd(1);
1107  int k_lo = vbx.smallEnd(2); int k_hi = vbx.bigEnd(2);
1108 
1109  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
1110  const Array4<int>& mask_arr = cc_mask.array(mfi);
1111 
1112  auto domlo = lbound(domain);
1113  auto domhi = ubound(domain);
1114 
1115  ParallelFor(planex_lo, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
1116  {
1117  int lj = amrex::min(amrex::max(j, domlo.y), domhi.y);
1118  int lk = amrex::min(amrex::max(k, domlo.z), domhi.z);
1119  if ((mask_arr(i,j,k) == is_notcovered && mask_arr(i_lo,lj,lk) != is_notcovered) ||
1120  (mask_arr(i,j,k) == is_physbnd && i < domlo.x && impose_phys_bcs)) {
1121  for (int n = 0; n < ncomp; n++) {
1122  mu_turb(i,j,k,n) = mu_turb(i_lo,lj,lk,n);
1123  }
1124  }
1125  });
1126  ParallelFor(planex_hi, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
1127  {
1128  int lj = amrex::min(amrex::max(j, domlo.y), domhi.y);
1129  int lk = amrex::min(amrex::max(k, domlo.z), domhi.z);
1130  if ((mask_arr(i,j,k) == is_notcovered && mask_arr(i_hi,lj,lk) != is_notcovered) ||
1131  (mask_arr(i,j,k) == is_physbnd && i > domhi.x && impose_phys_bcs)) {
1132  for (int n = 0; n < ncomp; n++) {
1133  mu_turb(i,j,k,n) = mu_turb(i_hi,lj,lk,n);
1134  }
1135  }
1136  });
1137  ParallelFor(planey_lo, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
1138  {
1139  int lk = amrex::min(amrex::max(k, domlo.z), domhi.z);
1140  if ((mask_arr(i,j,k) == is_notcovered && mask_arr(i,j_lo,lk) != is_notcovered) ||
1141  (mask_arr(i,j,k) == is_physbnd && j < domlo.y && impose_phys_bcs)) {
1142  for (int n = 0; n < ncomp; n++) {
1143  mu_turb(i,j,k,n) = mu_turb(i,j_lo,lk,n);
1144  }
1145  }
1146  });
1147  ParallelFor(planey_hi, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
1148  {
1149  int lk = amrex::min(amrex::max(k, domlo.z), domhi.z);
1150  if ((mask_arr(i,j,k) == is_notcovered && mask_arr(i,j_hi,lk) != is_notcovered)||
1151  (mask_arr(i,j,k) == is_physbnd && j > domhi.y && impose_phys_bcs)) {
1152  for (int n = 0; n < ncomp; n++) {
1153  mu_turb(i,j,k,n) = mu_turb(i,j_hi,lk,n);
1154  }
1155  }
1156  });
1157  ParallelFor(planez_lo, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
1158  {
1159  if ((mask_arr(i,j,k) == is_notcovered && mask_arr(i,j,k_lo) != is_notcovered) ||
1160  (mask_arr(i,j,k) == is_physbnd && k < domlo.z && impose_phys_bcs)) {
1161  for (int n = 0; n < ncomp; n++) {
1162  mu_turb(i,j,k,n) = mu_turb(i,j,k_lo,n);
1163  }
1164  }
1165  });
1166  ParallelFor(planez_hi, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
1167  {
1168  if ((mask_arr(i,j,k) == is_notcovered && mask_arr(i,j,k_hi) != is_notcovered) ||
1169  (mask_arr(i,j,k) == is_physbnd && k > domhi.z && impose_phys_bcs)) {
1170  for (int n = 0; n < ncomp; n++) {
1171  mu_turb(i,j,k,n) = mu_turb(i,j,k_hi,n);
1172  }
1173  }
1174  });
1175  //
1176  // Edge and corner ghost cells at a C/F boundary are skipped above when every face
1177  // neighbour toward the valid box is itself a C/F ghost cell, e.g. (i_lo-1,j_lo-1).
1178  // Fill these from a cell that is valid at this level (in this grid or another),
1179  // stepping toward the valid box in two directions (xy, xz, yz) before all three.
1180  // The sources are never written in this block, and taking covered cells as well as
1181  // our own keeps the result independent of how the level is divided into grids.
1182  //
1183  ParallelFor(mfi.growntilebox(1), [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
1184  {
1185  if (mask_arr(i,j,k) != is_notcovered) { return; }
1186 
1187  int si = amrex::min(amrex::max(i, i_lo), i_hi);
1188  int sj = amrex::min(amrex::max(j, j_lo), j_hi);
1189  int sk = amrex::min(amrex::max(k, k_lo), k_hi);
1190  bool gx = (si != i);
1191  bool gy = (sj != j);
1192  bool gz = (sk != k);
1193 
1194  // Already filled by one of the plane kernels above
1195  if (gx && mask_arr(si,j,k) != is_notcovered) { return; }
1196  if (gy && mask_arr(i,sj,k) != is_notcovered) { return; }
1197  if (gz && mask_arr(i,j,sk) != is_notcovered) { return; }
1198 
1199  int ii = si; int jj = sj; int kk = sk;
1200  if (gx && gy && mask_arr(si,sj,k ) != is_notcovered) {
1201  kk = k;
1202  } else if (gx && gz && mask_arr(si,j ,sk) != is_notcovered) {
1203  jj = j;
1204  } else if (gy && gz && mask_arr(i ,sj,sk) != is_notcovered) {
1205  ii = i;
1206  }
1207  for (int n = 0; n < ncomp; n++) {
1208  mu_turb(i,j,k,n) = mu_turb(ii,jj,kk,n);
1209  }
1210  });
1211  } // mfi
1212 
1213  eddyViscosity.FillBoundary(geom.periodicity());
1214  }
1215 }
void ComputeDiffusivityMRF(const MultiFab &xvel, const MultiFab &yvel, const MultiFab &cons_in, MultiFab &eddyViscosity, const Geometry &geom, const TurbChoice &turbChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, bool use_terrain_fitted_coords, bool use_moisture, int level, const BCRec *bc_ptr, bool, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const MoistureComponentIndices &moisture_indices, const MultiFab *terrain_blank)
Definition: ERF_ComputeDiffusivityMRF.cpp:34
void ComputeDiffusivityMYJ(double dt, const MultiFab &xvel, const MultiFab &yvel, MultiFab &cons_in, MultiFab &eddyViscosity, const Geometry &geom, const TurbChoice &turbChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, bool use_terrain_fitted_coords, bool use_moisture, int level, const BCRec *bc_ptr, bool, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const MoistureComponentIndices &moisture_indices)
Definition: ERF_ComputeDiffusivityMYJ.cpp:33
void ComputeDiffusivityMYNN25(const MultiFab &xvel, const MultiFab &yvel, const MultiFab &cons_in, MultiFab &eddyViscosity, const Geometry &geom, const TurbChoice &turbChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, bool use_terrain_fitted_coords, bool use_moisture, int level, const BCRec *bc_ptr, bool, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const MoistureComponentIndices &moisture_indices)
Definition: ERF_ComputeDiffusivityMYNN25.cpp:31
void ComputeDiffusivityMYNNEDMF(const MultiFab &xvel, const MultiFab &yvel, const MultiFab &cons_in, MultiFab &eddyViscosity, const Geometry &geom, const TurbChoice &turbChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, bool use_terrain_fitted_coords, bool use_moisture, int level, const BCRec *bc_ptr, bool, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const MoistureComponentIndices &moisture_indices)
Compute eddy viscosity and diffusivity coefficients using the MYNN-EDMF closure.
Definition: ERF_ComputeDiffusivityMYNNEDMF.cpp:4194
void ComputeDiffusivityYSUNew(const MultiFab &xvel, const MultiFab &yvel, const MultiFab &cons_in, MultiFab &eddyViscosity, const Geometry &geom, const TurbChoice &turbChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, bool use_terrain_fitted_coords, bool use_moisture, int level, const BCRec *bc_ptr, bool, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const MoistureComponentIndices &moisture_indices, const MultiFab *qheating_rates, const MultiFab *terrain_blank)
Definition: ERF_ComputeDiffusivityYSUNew.cpp:44
void ComputeDiffusivityYSU(const MultiFab &xvel, const MultiFab &yvel, const MultiFab &cons_in, MultiFab &eddyViscosity, const Geometry &geom, const TurbChoice &turbChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, bool use_terrain_fitted_coords, bool use_moisture, int level, const BCRec *bc_ptr, bool, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const MoistureComponentIndices &moisture_indices)
Definition: ERF_ComputeDiffusivityYSU.cpp:31
void ComputeTurbulentViscosityRANS(int level, Vector< std::unique_ptr< MultiFab >> &Tau_lev, const MultiFab &cons_in, const MultiFab &wdist, MultiFab &eddyViscosity, MultiFab &Diss, const Geometry &geom, bool use_terrain_fitted_coords, const std::unique_ptr< MultiFab > &z_phys_nd, const TurbChoice &turbChoice, const Real const_grav, std::unique_ptr< SurfaceLayer > &SurfLayer, const MultiFab *z_0, const CanopyTurbulence &canopy, const MultiFab *forest_drag, const MultiFab *ib_blank, bool flat_wall_diss, Real ib_small_volfrac, Real ib_z0)
Definition: ERF_ComputeTurbulentViscosity.cpp:631
void ComputeTurbulentViscosityLES_EB(Vector< std::unique_ptr< MultiFab >> &Tau_lev, const MultiFab &cons_in, MultiFab &eddyViscosity, MultiFab &Hfx1, MultiFab &Hfx2, MultiFab &Hfx3, const Geometry &geom, Vector< std::unique_ptr< MultiFab >> &mapfac, const TurbChoice &turbChoice, const Real const_grav, [[maybe_unused]] const SolverChoice &solverChoice, std::unique_ptr< SurfaceLayer > &, const MoistureComponentIndices &moisture_indices, const eb_ &ebfact, const MultiFab *xvel, const MultiFab *yvel)
Definition: ERF_ComputeTurbulentViscosity.cpp:358
void ComputeTurbulentViscosity(double dt, const MultiFab &xvel, const MultiFab &yvel, Vector< std::unique_ptr< MultiFab >> &Tau_lev, MultiFab &cons_in, const MultiFab &wdist, MultiFab &eddyViscosity, MultiFab &Hfx1, MultiFab &Hfx2, MultiFab &Hfx3, MultiFab &Diss, const Geometry &geom, Vector< std::unique_ptr< MultiFab >> &mapfac, const std::unique_ptr< MultiFab > &z_phys_nd, const std::unique_ptr< MultiFab > &z_phys_cc, const SolverChoice &solverChoice, std::unique_ptr< SurfaceLayer > &SurfLayer, const MultiFab *z_0, const bool &use_terrain_fitted_coords, const bool &use_moisture, int level, const BCRec *bc_ptr, const eb_ &ebfact, bool vert_only, const MultiFab *qheating_rates, const MultiFab *terrain_blank, const MultiFab *forest_drag)
Definition: ERF_ComputeTurbulentViscosity.cpp:937
void ComputeTurbulentViscosityLES(Vector< std::unique_ptr< MultiFab >> &Tau_lev, const MultiFab &cons_in, MultiFab &eddyViscosity, MultiFab &Diss, const Geometry &geom, bool use_terrain_fitted_coords, Vector< std::unique_ptr< MultiFab >> &mapfac, const std::unique_ptr< MultiFab > &z_phys_nd, const TurbChoice &turbChoice, const Real const_grav, std::unique_ptr< SurfaceLayer > &, const MoistureComponentIndices &moisture_indices, const MultiFab *xvel, const MultiFab *yvel)
Definition: ERF_ComputeTurbulentViscosity.cpp:36
const bool use_moisture
Definition: ERF_InitCustomPert_ABL.H:71
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
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);})
amrex::Real Real
Definition: ERF_ShocInterface.H:19
Real z_0
Definition: ERF_UpdateWSubsidence_Bomex.H:10
@ None
Definition: ERF_LSM_Types.H:6
@ xvel
Definition: ERF_IndexDefines.H:255
@ yvel
Definition: ERF_IndexDefines.H:256
amrex::Vector< TurbChoice > turbChoice
Turbulence options for each AMR level.
Definition: ERF_DataStruct.H:2620
bool if_fraction_stress
erf.if_wall_form = fraction_stress: the wall stress of ERF_ImmersedWallCell.H in the wall cell of eac...
Definition: ERF_DataStruct.H:2714
amrex::Real gravity
Effective gravitational acceleration.
Definition: ERF_DataStruct.H:2736
amrex::Real if_small_volfrac
Immersed-forcing solid fraction below which a cell or face counts as fluid; read from eb2....
Definition: ERF_DataStruct.H:2723
amrex::Real if_z0
Immersed-forcing roughness length [m].
Definition: ERF_DataStruct.H:2706
static TerrainType terrain_type
Terrain or immersed-boundary representation.
Definition: ERF_DataStruct.H:2589
bool if_flat_wall_dissipation
Partial fraction-stress wall cells dissipate TKE with the length scale of flat ground's first cell; o...
Definition: ERF_DataStruct.H:2718
CanopyTurbulence canopyTurb
Definition: ERF_DataStruct.H:3040
MoistureComponentIndices moisture_indices
Index map of the moisture data carried by the active scheme: conserved-state components for the speci...
Definition: ERF_DataStruct.H:2993
Definition: ERF_TurbStruct.H:119
RANSType rans_type
Selected RANS closure.
Definition: ERF_TurbStruct.H:877
bool uses_shoc_family() const noexcept
Query whether this level uses any SHOC-family PBL scheme.
Definition: ERF_TurbStruct.H:933
LESType les_type
Selected LES closure.
Definition: ERF_TurbStruct.H:826
PBLType pbl_type
Selected PBL closure.
Definition: ERF_TurbStruct.H:909

Referenced by ERF::advance_dycore().

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

◆ ComputeTurbulentViscosityLES()

void ComputeTurbulentViscosityLES ( Vector< std::unique_ptr< MultiFab >> &  Tau_lev,
const MultiFab &  cons_in,
MultiFab &  eddyViscosity,
MultiFab &  Diss,
const Geometry &  geom,
bool  use_terrain_fitted_coords,
Vector< std::unique_ptr< MultiFab >> &  mapfac,
const std::unique_ptr< MultiFab > &  z_phys_nd,
const TurbChoice &  turbChoice,
const Real  const_grav,
std::unique_ptr< SurfaceLayer > &  ,
const MoistureComponentIndices &  moisture_indices,
const MultiFab *  xvel,
const MultiFab *  yvel 
)

Function for computing the turbulent viscosity with LES.

Parameters
[in]Tau_levstrain at this level
[in]cons_incell center conserved quantities
[out]eddyViscosityturbulent viscosity
[out]Dissdissipation of turbulent kinetic energy
[in]geomproblem geometry
[in]use_terrain_fitted_coordsflag for terrain-fitted coordinates
[in]mapfacmap factors
[in]z_phys_ndnodal physical z coordinates
[in]turbChoicecontainer with turbulence parameters
[in]const_gravgravitational acceleration
[in]moisture_indicesmoisture component indices
[in]xvelx-direction velocity (for moist Ri correction)
[in]yvely-direction velocity (for moist Ri correction)
47 {
48  const GpuArray<Real, AMREX_SPACEDIM> cellSizeInv = geom.InvCellSizeArray();
49  const Box& domain = geom.Domain();
50 
51  Real inv_Pr_t = turbChoice.Pr_t_inv;
52  Real inv_Sc_t = turbChoice.Sc_t_inv;
53  Real inv_sigma_k = one / turbChoice.sigma_k;
54 
55  bool use_thetav_grad = (turbChoice.strat_type == StratType::thetav);
56  bool use_thetal_grad = (turbChoice.strat_type == StratType::thetal);
57 
58  bool isotropic = turbChoice.mix_isotropic;
59 
60  // SMAGORINSKY: Fill Kturb for momentum in horizontal and vertical
61  //***********************************************************************************
62  if (turbChoice.les_type == LESType::Smagorinsky)
63  {
64  Real Cs = turbChoice.Cs;
65  bool smag2d = turbChoice.smag2d;
66 
67  // Opt-in limits of WRF's smag2d_km on the horizontal viscosity (see
68  // Smag2DLimitedViscosity). The slope limiter needs the terrain slope, so it acts
69  // on terrain-fitted meshes only; SolverChoice aborts if it is asked for elsewhere.
70  bool l_smag2d_slope = smag2d && turbChoice.smag2d_slope_limiter && use_terrain_fitted_coords;
71  Real l_smag2d_cap = (smag2d) ? turbChoice.smag2d_kh_cap : zero;
72  bool l_smag2d_limit = l_smag2d_slope || (l_smag2d_cap > zero);
73 
74  // Define variables required inside device lambdas (scalars only)
75  Real l_abs_g = const_grav;
76  Real l_Ri_crit = turbChoice.Ri_crit;
77  bool l_use_Ri_corr = turbChoice.use_Ri_correction;
78  bool l_has_xvel = (xvel != nullptr);
79  bool l_has_yvel = (yvel != nullptr);
80 
81 #ifdef _OPENMP
82 #pragma omp parallel if (Gpu::notInLaunchRegion())
83 #endif
84  for (MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi)
85  {
86  Box bxcc = mfi.growntilebox(1) & domain;
87  // NOTE: the closures deliberately store no subgrid heat flux. hfx_z (Hfx3)
88  // is z-nodal, (0,0,1), while the closure's -K dtheta/dz is cell-centred,
89  // and the theta diffusion of every RK stage overwrites all z-faces (the
90  // surface layer the bottom face) before anything reads them, so the value
91  // written here was both misplaced and unused. The TKE buoyancy source
92  // reads the face fluxes of that diffusion (ERF_TurbKESources.H).
93  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
94  const Array4<Real const >& cell_data = cons_in.array(mfi);
95  const StrainArrays tau = strain_arrays(Tau_lev, mfi);
96  const Array4<Real const> tau11 = tau.tau11, tau22 = tau.tau22, tau33 = tau.tau33;
97  const Array4<Real const> tau12 = tau.tau12, tau13 = tau.tau13, tau23 = tau.tau23;
98  Array4<Real const> mf_u = mapfac[MapFacType::u_x]->const_array(mfi);
99  Array4<Real const> mf_v = mapfac[MapFacType::v_y]->const_array(mfi);
100  Array4<Real const> z_nd_arr = z_phys_nd->const_array(mfi);
101 
102  Array4<Real const> u_arr = (l_has_xvel) ? xvel->const_array(mfi) : Array4<Real const>{};
103  Array4<Real const> v_arr = (l_has_yvel) ? yvel->const_array(mfi) : Array4<Real const>{};
104 
105  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
106  {
107  // =====================================================================
108  // 1. STRAIN RATE MAGNITUDE CALCULATION
109  // =====================================================================
110  Real SmnSmn;
111  if (smag2d) {
112  SmnSmn = ComputeSmnSmn2D(i,j,k,tau11,tau22,tau12);
113  } else {
114  SmnSmn = ComputeSmnSmn(i,j,k,tau11,tau22,tau33,tau12,tau13,tau23);
115  }
116  Real strain_rate_magnitude = std::sqrt(two * SmnSmn);
117 
118  // =====================================================================
119  // 2. GRID SCALE CALCULATION (filter width Δ)
120  // =====================================================================
121  Real dxInv = cellSizeInv[0];
122  Real dyInv = cellSizeInv[1];
123  Real dzInv = cellSizeInv[2];
124  if (use_terrain_fitted_coords) {
125  dzInv /= Compute_h_zeta_AtCellCenter(i,j,k, cellSizeInv, z_nd_arr);
126  }
127 
128  Real Delta;
129  Real DeltaH;
130  if (isotropic) {
131  Real cellVolMsf = one / (dxInv * mf_u(i,j,0) * dyInv * mf_v(i,j,0) * dzInv);
132  Delta = std::cbrt(cellVolMsf);
133  DeltaH = Delta;
134  } else {
135  Delta = one / dzInv;
136  DeltaH = std::sqrt(one / (dxInv * mf_u(i,j,0) * dyInv * mf_v(i,j,0)));
137  }
138 
139  Real rho = cell_data(i, j, k, Rho_comp);
140  Real CsDeltaSqr_h = Cs * Cs * DeltaH * DeltaH;
141  Real CsDeltaSqr_v = Cs * Cs * Delta * Delta;
142 
143  Real nu_turb_base_h = CsDeltaSqr_h * strain_rate_magnitude;
144  Real nu_turb_base_v = CsDeltaSqr_v * strain_rate_magnitude;
145 
146  // WRF smag2d_km limits on K_h only, applied before the scalar diffusivities
147  // (Theta_h, Scalar_h, Q_h) are derived from Mom_h below; Mom_v is unchanged.
148  if (l_smag2d_limit) {
149  Real alpha = one;
150  if (l_smag2d_slope) {
152  }
153  nu_turb_base_h = Smag2DLimitedViscosity(nu_turb_base_h, strain_rate_magnitude,
154  DeltaH, alpha, l_smag2d_cap, l_smag2d_slope);
155  }
156 
157  Real stability_factor = one;
158 
159  if (l_use_Ri_corr && l_has_xvel && l_has_yvel) {
160  Real N2 = ComputeN2(i, j, k, dzInv, l_abs_g, cell_data, moisture_indices);
161  Real S2_vert = ComputeVerticalShear2(i, j, k, dzInv, u_arr, v_arr);
162  Real Ri = ComputeRichardson(N2, S2_vert);
163  stability_factor = StabilityFunction(Ri, l_Ri_crit);
164  }
165 
166  if (isotropic) {
167  mu_turb(i, j, k, EddyDiff::Mom_h) = rho * nu_turb_base_h * stability_factor;
168  mu_turb(i, j, k, EddyDiff::Mom_v) = rho * nu_turb_base_v * stability_factor;
169  } else {
170  mu_turb(i, j, k, EddyDiff::Mom_h) = rho * nu_turb_base_h;
171  mu_turb(i, j, k, EddyDiff::Mom_v) = rho * nu_turb_base_v * stability_factor;
172  }
173  });
174  }
175  }
176  // DEARDORFF: Fill Kturb for momentum in horizontal and vertical
177  //***********************************************************************************
178  else if (turbChoice.les_type == LESType::Deardorff)
179  {
180  const Real l_C_k = turbChoice.Ck;
181  const Real l_C_e = turbChoice.Ce;
182  const Real l_C_e_wall = turbChoice.Ce_wall;
183  const Real Ce_lcoeff = amrex::max(amrex::Real(zero), l_C_e - Real(1.9)*l_C_k);
184  const Real l_abs_g = const_grav;
185 
186  // Clamped divisor: the select is if-converted, so 1/theta_ref runs even
187  // when theta_ref = 0 and would trip fpe_trap_zero (see ERF_SetupDiff.H)
188  const bool use_ref_theta = (turbChoice.theta_ref > 0);
189  const Real inv_theta_ref = one / amrex::max(turbChoice.theta_ref, std::numeric_limits<Real>::min());
190  const Real l_inv_theta0 = (use_ref_theta) ? inv_theta_ref : one;
191 
192 #ifdef _OPENMP
193 #pragma omp parallel if (Gpu::notInLaunchRegion())
194 #endif
195  for ( MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi)
196  {
197  Box bxcc = mfi.tilebox();
198 
199  // NOTE: the closures deliberately store no subgrid heat flux. hfx_z (Hfx3)
200  // is z-nodal, (0,0,1), while the closure's -K dtheta/dz is cell-centred,
201  // and the theta diffusion of every RK stage overwrites all z-faces (the
202  // surface layer the bottom face) before anything reads them, so the value
203  // written here was both misplaced and unused. The TKE buoyancy source
204  // reads the face fluxes of that diffusion (ERF_TurbKESources.H).
205  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
206  const Array4<Real>& diss = Diss.array(mfi);
207 
208  const Array4<Real const > &cell_data = cons_in.array(mfi);
209 
210  Array4<Real const> mf_u = mapfac[MapFacType::u_x]->const_array(mfi);
211  Array4<Real const> mf_v = mapfac[MapFacType::v_y]->const_array(mfi);
212 
213  Array4<Real const> z_nd_arr = z_phys_nd->const_array(mfi);
214 
215  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
216  {
217  Real dxInv = cellSizeInv[0];
218  Real dyInv = cellSizeInv[1];
219  Real dzInv = cellSizeInv[2];
220  if (use_terrain_fitted_coords) {
221  // the terrain grid is only deformed in z for now
222  dzInv /= Compute_h_zeta_AtCellCenter(i,j,k, cellSizeInv, z_nd_arr);
223  }
224  Real Delta;
225  if (isotropic) {
226  Real cellVolMsf = one / (dxInv * mf_u(i,j,0) * dyInv * mf_v(i,j,0) * dzInv);
227  Delta = std::cbrt(cellVolMsf);
228  } else {
229  Delta = one / dzInv;
230  }
231 
232  Real dtheta_dz;
233  if (use_thetav_grad) {
234  dtheta_dz = myhalf * ( GetThetav(i, j, k+1, cell_data, moisture_indices)
235  -GetThetav(i, j, k-1, cell_data, moisture_indices) )*dzInv;
236  } else if (use_thetal_grad) {
237  dtheta_dz = myhalf * ( GetThetal(i, j, k+1, cell_data, moisture_indices)
238  -GetThetal(i, j, k-1, cell_data, moisture_indices) )*dzInv;
239  } else {
240  dtheta_dz = myhalf * ( cell_data(i, j, k+1, RhoTheta_comp) / cell_data(i, j, k+1, Rho_comp)
241  -cell_data(i, j, k-1, RhoTheta_comp) / cell_data(i, j, k-1, Rho_comp) )*dzInv;
242  }
243 
244  // Calculate stratification-dependent mixing length (Deardorff 1980, Eqn. 10a)
245  Real E = amrex::max(cell_data(i,j,k,RhoKE_comp)/cell_data(i,j,k,Rho_comp),Real(0.0));
246  Real stratification = l_abs_g * dtheta_dz * l_inv_theta0;
247  if (!use_ref_theta) {
248  // l_inv_theta0 == 1, divide by actual theta
249  stratification *= cell_data(i,j,k,Rho_comp) /
250  cell_data(i,j,k,RhoTheta_comp);
251  }
252 
253  // Following WRF, the stratification effects are applied to the vertical length scales
254  // in the case of anisotropic mixing
255  Real length;
257  if (stratification <= eps) {
258  length = Delta; // cbrt(dx*dy*dz) -or- dz
259  } else {
260  length = Real(0.76) * std::sqrt(E / amrex::max(stratification,eps));
261  // mixing length should be _reduced_ for stable stratification
262  length = amrex::min(length, Delta);
263  // following WRF, make sure the mixing length isn't too small
264  length = amrex::max(length, Real(0.001) * Delta);
265  }
266 
267  Real DeltaH = (isotropic) ? length : std::sqrt(one / (dxInv * mf_u(i,j,0) * dyInv * mf_v(i,j,0)));
268 
269  Real Pr_inv_v = (one + two*length/Delta);
270  Real Pr_inv_h = (isotropic) ? Pr_inv_v : inv_Pr_t;
271 
272  // Calculate eddy diffusivities
273  // K = rho * C_k * l * KE^(1/2)
274  mu_turb(i,j,k,EddyDiff::Mom_h) = cell_data(i,j,k,Rho_comp) * l_C_k * DeltaH * std::sqrt(E);
275  mu_turb(i,j,k,EddyDiff::Mom_v) = cell_data(i,j,k,Rho_comp) * l_C_k * length * std::sqrt(E);
276  // KH = (1 + 2*l/delta) * mu_turb
277  mu_turb(i,j,k,EddyDiff::Theta_h) = Pr_inv_h * mu_turb(i,j,k,EddyDiff::Mom_h);
278  mu_turb(i,j,k,EddyDiff::Theta_v) = Pr_inv_v * mu_turb(i,j,k,EddyDiff::Mom_v);
279  // Store lengthscale for TKE source terms
280  mu_turb(i,j,k,EddyDiff::Turb_lengthscale) = length;
281 
282  // Calculate SFS quantities
283  // - dissipation
284  Real Ce;
285  if ((l_C_e_wall > 0) && (k==0)) {
286  Ce = l_C_e_wall;
287  } else {
288  Ce = Real(1.9)*l_C_k + Ce_lcoeff*length / Delta;
289  }
290  diss(i,j,k) = cell_data(i,j,k,Rho_comp) * Ce * std::pow(E,Real(1.5)) / length;
291  });
292  }
293  }
294 
295  // Extrapolate Kturb in x/y, fill remaining elements (relevant to lev==0)
296  //***********************************************************************************
297  int ngc(1);
298  // EddyDiff mapping : Theta_h KE_h Scalar_h Q_h
299  Vector<Real> Factors = {inv_Pr_t, inv_sigma_k, inv_Sc_t, inv_Sc_t}; // alpha = mu/Pr
300  Gpu::AsyncVector<Real> d_Factors; d_Factors.resize(Factors.size());
301  Gpu::copy(Gpu::hostToDevice, Factors.begin(), Factors.end(), d_Factors.begin());
302  Real* fac_ptr = d_Factors.data();
303 
304  const bool use_KE = ( turbChoice.les_type == LESType::Deardorff );
305 
306 #ifdef _OPENMP
307 #pragma omp parallel if (Gpu::notInLaunchRegion())
308 #endif
309  for ( MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi)
310  {
311  Box bxcc = mfi.tilebox();
312  Box planex = bxcc; planex.setSmall(0, 1); planex.setBig(0, ngc); planex.grow(1,1);
313  Box planey = bxcc; planey.setSmall(1, 1); planey.setBig(1, ngc); planey.grow(0,1);
314  //
315  // NOTE: growntilebox, not the tilebox grown by hand. Growing by hand pushes every tile
316  // ngc cells past its own share of the grid, so once the grid is tiled two tiles
317  // store to the same mu_turb cells -- a data race under OpenMP, even though both
318  // store the same value. The union of the tiles, and the values stored, are
319  // unchanged, and this is identical to the old expression with one tile per grid.
320  //
321  bxcc = mfi.growntilebox(IntVect(ngc,ngc,0));
322 
323  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
324 
325  for (auto n = 1; n < EddyDiff::Mom_v; ++n) {
326  int offset = EddyDiff::Mom_v;
327  switch (n)
328  {
329  case EddyDiff::KE_h:
330  if (use_KE) {
331  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
332  {
333  int indx = n;
334  int indx_v = indx + offset;
335  mu_turb(i,j,k,indx) = mu_turb(i,j,k,EddyDiff::Mom_h) * fac_ptr[indx-1];
336  mu_turb(i,j,k,indx_v) = mu_turb(i,j,k,EddyDiff::Mom_v) * fac_ptr[indx-1];
337  });
338  }
339  break;
340  default:
341  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
342  {
343  int indx = n;
344  int indx_v = indx + offset;
345 
346  // NOTE: Theta_h, Theta_v have already been set for Deardorff
347  if (!(indx_v == EddyDiff::Theta_v && use_KE)) {
348  mu_turb(i,j,k,indx) = mu_turb(i,j,k,EddyDiff::Mom_h) * fac_ptr[indx-1];
349  mu_turb(i,j,k,indx_v) = mu_turb(i,j,k,EddyDiff::Mom_v) * fac_ptr[indx-1];
350  }
351  });
352  break;
353  }
354  }
355  }
356 }
@ tau12
Definition: ERF_DataStruct.H:49
@ tau23
Definition: ERF_DataStruct.H:49
@ tau33
Definition: ERF_DataStruct.H:49
@ tau22
Definition: ERF_DataStruct.H:49
@ tau11
Definition: ERF_DataStruct.H:49
@ tau13
Definition: ERF_DataStruct.H:49
@ v_y
Definition: ERF_DataStruct.H:39
@ u_x
Definition: ERF_DataStruct.H:38
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeSmnSmn2D(int &i, int &j, int &k, const amrex::Array4< amrex::Real const > &tau11, const amrex::Array4< amrex::Real const > &tau22, const amrex::Array4< amrex::Real const > &tau12)
Definition: ERF_EddyViscosity.H:297
StrainArrays strain_arrays(amrex::Vector< std::unique_ptr< amrex::MultiFab >> &Tau_lev, const amrex::MFIter &mfi)
Definition: ERF_EddyViscosity.H:164
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeSmnSmn(int &i, int &j, int &k, const amrex::Array4< amrex::Real const > &tau11, const amrex::Array4< amrex::Real const > &tau22, const amrex::Array4< amrex::Real const > &tau33, const amrex::Array4< amrex::Real const > &tau12, const amrex::Array4< amrex::Real const > &tau13, const amrex::Array4< amrex::Real const > &tau23)
Definition: ERF_EddyViscosity.H:133
#define Rho_comp
Definition: ERF_IndexDefines.H:53
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:54
#define RhoKE_comp
Definition: ERF_IndexDefines.H:55
amrex::GpuArray< Real, AMREX_SPACEDIM > dxInv
Definition: ERF_InitCustomPertVels_ParticleTests.H:17
const Real length
Definition: ERF_InitCustomPert_AnelasticWallDiffusion.H:16
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real GetThetav(const int &i, const int &j, const int &k, const amrex::Array4< amrex::Real const > &cell_data, const MoistureComponentIndices &moisture_indices)
Definition: ERF_MoistUtils.H:93
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real GetThetal(const int &i, const int &j, const int &k, const amrex::Array4< amrex::Real const > &cell_data, const MoistureComponentIndices &moisture_indices)
Definition: ERF_MoistUtils.H:127
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_FORCE_INLINE IntVect offset(const int face_dir, const int normal)
Definition: ERF_ReadBndryPlanes.cpp:36
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real StabilityFunction(amrex::Real Ri, amrex::Real Ri_crit)
Definition: ERF_RichardsonNumber.H:522
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeVerticalShear2(int i, int j, int k, amrex::Real dzInv, const amrex::Array4< const amrex::Real > &u, const amrex::Array4< const amrex::Real > &v)
Definition: ERF_RichardsonNumber.H:366
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeRichardson(amrex::Real N2_moist, amrex::Real S2_vert)
Definition: ERF_RichardsonNumber.H:506
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeN2(int i, int j, int k, amrex::Real dzInv, amrex::Real const_grav, const amrex::Array4< const amrex::Real > &cell_data, const MoistureComponentIndices &moisture_indices)
Definition: ERF_RichardsonNumber.H:130
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real Smag2DLimitedViscosity(amrex::Real nu, amrex::Real strain, amrex::Real DeltaH, amrex::Real alpha, amrex::Real kh_cap, bool use_slope) noexcept
Definition: ERF_TerrainDiffusionLimits.H:110
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real TerrainSlopeFactor(const TerrainCellDrops &d) noexcept
Definition: ERF_TerrainDiffusionLimits.H:78
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE TerrainCellDrops ComputeTerrainCellDrops(int i, int j, int k, const amrex::Array4< const amrex::Real > &z_nd) noexcept
Definition: ERF_TerrainDiffusionLimits.H:44
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_h_zeta_AtCellCenter(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:201
@ Theta_v
Definition: ERF_IndexDefines.H:290
@ Turb_lengthscale
Definition: ERF_IndexDefines.H:294
@ Mom_h
Definition: ERF_IndexDefines.H:284
@ Mom_v
Definition: ERF_IndexDefines.H:289
@ Theta_h
Definition: ERF_IndexDefines.H:285
@ KE_h
Definition: ERF_IndexDefines.H:286
@ rho
Definition: ERF_Kessler.H:25
@ alpha
Definition: ERF_SLM.H:151
real(c_double), parameter epsilon
Definition: ERF_module_model_constants.F90:12
Definition: ERF_EddyViscosity.H:158
amrex::Array4< amrex::Real const > tau23
Definition: ERF_EddyViscosity.H:159
amrex::Array4< amrex::Real const > tau13
Definition: ERF_EddyViscosity.H:159
amrex::Array4< amrex::Real const > tau12
Definition: ERF_EddyViscosity.H:159
amrex::Array4< amrex::Real const > tau11
Definition: ERF_EddyViscosity.H:159
amrex::Array4< amrex::Real const > tau22
Definition: ERF_EddyViscosity.H:159
amrex::Array4< amrex::Real const > tau33
Definition: ERF_EddyViscosity.H:159
StratType strat_type
Thermodynamic variable used for stability stratification.
Definition: ERF_TurbStruct.H:866
amrex::Real Ce
Deardorff dissipation coefficient.
Definition: ERF_TurbStruct.H:848
amrex::Real sigma_k
TKE diffusivity coefficient denominator.
Definition: ERF_TurbStruct.H:861
bool use_Ri_correction
Whether Richardson-number correction is applied.
Definition: ERF_TurbStruct.H:873
amrex::Real Ck
Deardorff eddy-viscosity coefficient.
Definition: ERF_TurbStruct.H:850
amrex::Real Ce_wall
Wall value for the Deardorff dissipation coefficient.
Definition: ERF_TurbStruct.H:849
amrex::Real theta_ref
Reference potential temperature for stable stratification.
Definition: ERF_TurbStruct.H:864
amrex::Real Sc_t_inv
Inverse turbulent Schmidt number.
Definition: ERF_TurbStruct.H:834
bool mix_isotropic
Whether subgrid mixing uses isotropic length scales.
Definition: ERF_TurbStruct.H:871
amrex::Real smag2d_kh_cap
Cap K_h <= smag2d_kh_cap * Delta_h for Smagorinsky-2D (m/s); 0 means no cap.
Definition: ERF_TurbStruct.H:842
amrex::Real Ri_crit
Critical Richardson number for stability correction.
Definition: ERF_TurbStruct.H:874
amrex::Real Cs
Smagorinsky model coefficient.
Definition: ERF_TurbStruct.H:837
bool smag2d_slope_limiter
Apply WRF's Smagorinsky-2D slope limiter (K_h / alpha or / alpha^2) on terrain-fitted meshes.
Definition: ERF_TurbStruct.H:841
amrex::Real Pr_t_inv
Inverse turbulent Prandtl number.
Definition: ERF_TurbStruct.H:830
bool smag2d
Whether the 2-D Smagorinsky formulation is used.
Definition: ERF_TurbStruct.H:838

Referenced by ComputeTurbulentViscosity().

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

◆ ComputeTurbulentViscosityLES_EB()

void ComputeTurbulentViscosityLES_EB ( Vector< std::unique_ptr< MultiFab >> &  Tau_lev,
const MultiFab &  cons_in,
MultiFab &  eddyViscosity,
MultiFab &  Hfx1,
MultiFab &  Hfx2,
MultiFab &  Hfx3,
const Geometry &  geom,
Vector< std::unique_ptr< MultiFab >> &  mapfac,
const TurbChoice &  turbChoice,
const Real  const_grav,
[[maybe_unused] ] const SolverChoice &  solverChoice,
std::unique_ptr< SurfaceLayer > &  ,
const MoistureComponentIndices &  moisture_indices,
const eb_ &  ebfact,
const MultiFab *  xvel,
const MultiFab *  yvel 
)
370 {
371  const GpuArray<Real, AMREX_SPACEDIM> cellSizeInv = geom.InvCellSizeArray();
372  Real dxInv = cellSizeInv[0];
373  Real dyInv = cellSizeInv[1];
374  Real dzInv = cellSizeInv[2];
375 
376  const Box& domain = geom.Domain();
377 
378  Real inv_Pr_t = turbChoice.Pr_t_inv;
379  Real inv_Sc_t = turbChoice.Sc_t_inv;
380  Real inv_sigma_k = one / turbChoice.sigma_k;
381 
382  bool isotropic = turbChoice.mix_isotropic;
383 
384  AMREX_ASSERT(turbChoice.les_type == LESType::Smagorinsky);
385 
386  // SMAGORINSKY: Fill Kturb for momentum in horizontal and vertical
387  //***********************************************************************************
388  if (turbChoice.les_type == LESType::Smagorinsky)
389  {
390  Real Cs = turbChoice.Cs;
391 
392  // Define variables required inside device lambdas (scalars only)
393  Real l_abs_g = const_grav;
394  Real l_Ri_crit = turbChoice.Ri_crit;
395  bool l_use_Ri_corr = turbChoice.use_Ri_correction;
396  bool l_has_xvel = (xvel != nullptr);
397  bool l_has_yvel = (yvel != nullptr);
398 
399 #ifdef _OPENMP
400 #pragma omp parallel if (Gpu::notInLaunchRegion())
401 #endif
402  for (MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi)
403  {
404  Box bxcc = mfi.growntilebox(1) & domain;
405  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
406  const Array4<Real>& hfx_x = Hfx1.array(mfi);
407  const Array4<Real>& hfx_y = Hfx2.array(mfi);
408  const Array4<Real>& hfx_z = Hfx3.array(mfi);
409  const Array4<Real const >& cell_data = cons_in.array(mfi);
410  const StrainArrays tau = strain_arrays(Tau_lev, mfi);
411  const Array4<Real const> tau11 = tau.tau11, tau22 = tau.tau22, tau33 = tau.tau33;
412  const Array4<Real const> tau12 = tau.tau12, tau13 = tau.tau13, tau23 = tau.tau23;
413  Array4<Real const> mf_u = mapfac[MapFacType::u_x]->const_array(mfi);
414  Array4<Real const> mf_v = mapfac[MapFacType::v_y]->const_array(mfi);
415 
416  Array4<Real const> u_arr = (l_has_xvel) ? xvel->const_array(mfi) : Array4<Real const>{};
417  Array4<Real const> v_arr = (l_has_yvel) ? yvel->const_array(mfi) : Array4<Real const>{};
418 
419  Array4<const EBCellFlag> c_cflag = (ebfact.get_const_factory())->getMultiEBCellFlagFab()[mfi].const_array();
420  Array4<const EBCellFlag> u_cflag = (ebfact.get_u_const_factory())->getMultiEBCellFlagFab()[mfi].const_array();
421  Array4<const EBCellFlag> v_cflag = (ebfact.get_v_const_factory())->getMultiEBCellFlagFab()[mfi].const_array();
422  Array4<const EBCellFlag> w_cflag = (ebfact.get_w_const_factory())->getMultiEBCellFlagFab()[mfi].const_array();
423  Array4<const Real > u_vfrac = (ebfact.get_u_const_factory())->getVolFrac().const_array(mfi);
424  Array4<const Real > v_vfrac = (ebfact.get_v_const_factory())->getVolFrac().const_array(mfi);
425 
426  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
427  {
428  if (!c_cflag(i,j,k).isCovered()) {
429  // =====================================================================
430  // 1. STRAIN RATE MAGNITUDE CALCULATION
431  // =====================================================================
432  Real SmnSmn = zero;
433  if (c_cflag(i,j,k).isRegular()) {
434  SmnSmn = ComputeSmnSmn(i,j,k,tau11,tau22,tau33,tau12,tau13,tau23);
435  } else if (c_cflag(i,j,k).isSingleValued()) {
436  SmnSmn = ComputeSmnSmn_EB(i,j,k,tau11,tau22,tau33,tau12,tau13,tau23,c_cflag,u_cflag,v_cflag,w_cflag);
437  }
438  Real strain_rate_magnitude = std::sqrt(two * SmnSmn);
439 
440  // =====================================================================
441  // 2. GRID SCALE CALCULATION (filter width Δ)
442  // =====================================================================
443  Real Delta;
444  Real DeltaH;
445  if (isotropic) {
446  Real cellVolMsf = one / (dxInv * mf_u(i,j,0) * dyInv * mf_v(i,j,0) * dzInv);
447  Delta = std::cbrt(cellVolMsf);
448  DeltaH = Delta;
449  } else {
450  Delta = one / dzInv;
451  DeltaH = std::sqrt(one / (dxInv * mf_u(i,j,0) * dyInv * mf_v(i,j,0)));
452  }
453 
454  Real rho = cell_data(i, j, k, Rho_comp);
455  Real CsDeltaSqr_h = Cs * Cs * DeltaH * DeltaH;
456  Real CsDeltaSqr_v = Cs * Cs * Delta * Delta;
457 
458  Real nu_turb_base_h = CsDeltaSqr_h * strain_rate_magnitude;
459  Real nu_turb_base_v = CsDeltaSqr_v * strain_rate_magnitude;
460 
461  Real stability_factor = one;
462 
463  if (l_use_Ri_corr && l_has_xvel && l_has_yvel) {
464  Real N2 = zero;
465  Real S2_vert = zero;
466  // Covered and multi-valued cells fall through both branches below
467  // with no shear computed, so start from "not valid" and let the
468  // branch that does the work say otherwise.
469  bool shear_is_valid = false;
470  if (c_cflag(i,j,k).isRegular()) {
471  N2 = ComputeN2(i, j, k, dzInv, l_abs_g, cell_data, moisture_indices);
472  S2_vert = ComputeVerticalShear2(i, j, k, dzInv, u_arr, v_arr);
473  shear_is_valid = true;
474  } else if (c_cflag(i,j,k).isSingleValued()) {
475  N2 = ComputeN2_EB(i, j, k, c_cflag, dzInv, l_abs_g, cell_data, moisture_indices);
476  S2_vert = ComputeVerticalShear2_EB(i, j, k, c_cflag, u_vfrac, v_vfrac, dzInv, u_arr, v_arr, shear_is_valid);
477  }
478  // When the EB geometry leaves no usable shear stencil we keep
479  // stability_factor = 1, i.e. no stratification damping: without a
480  // trustworthy gradient we would rather leave the Smagorinsky
481  // viscosity alone than suppress it on the strength of a Ri we do
482  // not believe. This is a deliberate choice -- damping to 0 instead
483  // would be just as defensible -- so change it knowingly.
484  if (shear_is_valid) {
485  Real Ri = ComputeRichardson(N2, S2_vert);
486  stability_factor = StabilityFunction(Ri, l_Ri_crit);
487  }
488  }
489 
490  if (isotropic) {
491  mu_turb(i, j, k, EddyDiff::Mom_h) = rho * nu_turb_base_h * stability_factor;
492  mu_turb(i, j, k, EddyDiff::Mom_v) = rho * nu_turb_base_v * stability_factor;
493  } else {
494  mu_turb(i, j, k, EddyDiff::Mom_h) = rho * nu_turb_base_h;
495  mu_turb(i, j, k, EddyDiff::Mom_v) = rho * nu_turb_base_v * stability_factor;
496  }
497 
498  Real dtheta_dz = zero;
499 
500  if (c_cflag(i,j,k).isSingleValued() && c_cflag(i,j,k+1).isCovered()) {
501  amrex::Real theta = cell_data(i, j, k, RhoTheta_comp) / rho;
502  amrex::Real theta_km1 = cell_data(i,j,k-1,RhoTheta_comp)/cell_data(i,j,k-1,Rho_comp);
503  amrex::Real theta_km2 = cell_data(i,j,k-2,RhoTheta_comp)/cell_data(i,j,k-2,Rho_comp);
504  dtheta_dz = (three*theta - Real(4.)*theta_km1 + theta_km2) * myhalf * dzInv;
505  } else if (c_cflag(i,j,k).isSingleValued() && c_cflag(i,j,k-1).isCovered()) {
506  amrex::Real theta = cell_data(i, j, k, RhoTheta_comp) / rho;
507  amrex::Real theta_kp1 = cell_data(i,j,k+1,RhoTheta_comp)/cell_data(i,j,k+1,Rho_comp);
508  amrex::Real theta_kp2 = cell_data(i,j,k+2,RhoTheta_comp)/cell_data(i,j,k+2,Rho_comp);
509  dtheta_dz = (-theta_kp2 + Real(4.)*theta_kp1 - three*theta) * myhalf * dzInv;
510  } else if (!c_cflag(i,j,k).isCovered()) {
511  amrex::Real theta_km1 = cell_data(i,j,k-1,RhoTheta_comp)/cell_data(i,j,k-1,Rho_comp);
512  amrex::Real theta_kp1 = cell_data(i,j,k+1,RhoTheta_comp)/cell_data(i,j,k+1,Rho_comp);
513  dtheta_dz = myhalf * (theta_kp1 - theta_km1) * dzInv;
514  }
515 
516  // The EB diffusion does not write hfx_z, so on EB terrain this is
517  // the only writer of the SGS heat flux output (the surface layer
518  // overwrites the bottom). Elsewhere the theta diffusion writes the
519  // face fluxes and the closures do not store a heat flux. This write
520  // keeps the z-nodal/cell-centred mismatch noted above; moving the EB
521  // heat-flux output to the EB face fluxes belongs with that diffusion.
522  hfx_x(i,j,k) = zero;
523  hfx_y(i,j,k) = zero;
524  hfx_z(i,j,k) = -inv_Pr_t * mu_turb(i,j,k,EddyDiff::Mom_v) * dtheta_dz;
525 
526  } else {
527 
528  hfx_x(i,j,k) = zero;
529  hfx_y(i,j,k) = zero;
530  hfx_z(i,j,k) = zero;
531  }// End of if not covered
532  });
533  }
534  }
535 
536  // Extrapolate Kturb in x/y, fill remaining elements (relevant to lev==0)
537  //***********************************************************************************
538  int ngc(1);
539  // EddyDiff mapping : Theta_h KE_h Scalar_h Q_h
540  Vector<Real> Factors = {inv_Pr_t, inv_sigma_k, inv_Sc_t, inv_Sc_t}; // alpha = mu/Pr
541  Gpu::AsyncVector<Real> d_Factors; d_Factors.resize(Factors.size());
542  Gpu::copy(Gpu::hostToDevice, Factors.begin(), Factors.end(), d_Factors.begin());
543  Real* fac_ptr = d_Factors.data();
544 
545  const bool use_KE = ( turbChoice.les_type == LESType::Deardorff );
546 
547 #ifdef _OPENMP
548 #pragma omp parallel if (Gpu::notInLaunchRegion())
549 #endif
550  for ( MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi)
551  {
552  Box bxcc = mfi.tilebox();
553  Box planex = bxcc; planex.setSmall(0, 1); planex.setBig(0, ngc); planex.grow(1,1);
554  Box planey = bxcc; planey.setSmall(1, 1); planey.setBig(1, ngc); planey.grow(0,1);
555  //
556  // NOTE: growntilebox, not the tilebox grown by hand. Growing by hand pushes every tile
557  // ngc cells past its own share of the grid, so once the grid is tiled two tiles
558  // store to the same mu_turb cells -- a data race under OpenMP, even though both
559  // store the same value. The union of the tiles, and the values stored, are
560  // unchanged, and this is identical to the old expression with one tile per grid.
561  //
562  bxcc = mfi.growntilebox(IntVect(ngc,ngc,0));
563 
564  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
565 
566  for (auto n = 1; n < EddyDiff::Mom_v; ++n) {
567  int offset = EddyDiff::Mom_v;
568  switch (n)
569  {
570  case EddyDiff::KE_h:
571  if (use_KE) {
572  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
573  {
574  int indx = n;
575  int indx_v = indx + offset;
576  mu_turb(i,j,k,indx) = mu_turb(i,j,k,EddyDiff::Mom_h) * fac_ptr[indx-1];
577  mu_turb(i,j,k,indx_v) = mu_turb(i,j,k,EddyDiff::Mom_v) * fac_ptr[indx-1];
578  });
579  }
580  break;
581  default:
582  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
583  {
584  int indx = n;
585  int indx_v = indx + offset;
586 
587  // NOTE: Theta_h, Theta_v have already been set for Deardorff
588  if (!(indx_v == EddyDiff::Theta_v && use_KE)) {
589  mu_turb(i,j,k,indx) = mu_turb(i,j,k,EddyDiff::Mom_h) * fac_ptr[indx-1];
590  mu_turb(i,j,k,indx_v) = mu_turb(i,j,k,EddyDiff::Mom_v) * fac_ptr[indx-1];
591  }
592  });
593  break;
594  }
595  }
596  }
597 }
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeSmnSmn_EB(int &i, int &j, int &k, const amrex::Array4< amrex::Real const > &tau11, const amrex::Array4< amrex::Real const > &tau22, const amrex::Array4< amrex::Real const > &tau33, const amrex::Array4< amrex::Real const > &tau12, const amrex::Array4< amrex::Real const > &tau13, const amrex::Array4< amrex::Real const > &tau23, const amrex::Array4< const amrex::EBCellFlag > &c_cflag, const amrex::Array4< const amrex::EBCellFlag > &u_cflag, const amrex::Array4< const amrex::EBCellFlag > &v_cflag, const amrex::Array4< const amrex::EBCellFlag > &w_cflag)
Definition: ERF_EddyViscosity.H:192
constexpr amrex::Real three
Definition: ERF_NumericalConstants.H:39
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeN2_EB(int i, int j, int k, const amrex::Array4< const amrex::EBCellFlag > &c_cflag, amrex::Real dzInv, amrex::Real const_grav, const amrex::Array4< const amrex::Real > &cell_data, const MoistureComponentIndices &moisture_indices)
Definition: ERF_RichardsonNumber.H:192
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real ComputeVerticalShear2_EB(int i, int j, int k, const amrex::Array4< const amrex::EBCellFlag > &c_cflag, const amrex::Array4< const amrex::Real > &u_vfrac, const amrex::Array4< const amrex::Real > &v_vfrac, amrex::Real dzInv, const amrex::Array4< const amrex::Real > &u, const amrex::Array4< const amrex::Real > &v, bool &shear_is_valid)
Definition: ERF_RichardsonNumber.H:403
const std::unique_ptr< amrex::EBFArrayBoxFactory > & get_const_factory() const noexcept
Return the cell-centered EB factory.
Definition: ERF_EB.H:124
amrex::EBFArrayBoxFactory const * get_w_const_factory() const noexcept
Return the native AMReX z-face EB factory.
Definition: ERF_EB.H:148
amrex::EBFArrayBoxFactory const * get_u_const_factory() const noexcept
Return the native AMReX x-face EB factory.
Definition: ERF_EB.H:144
amrex::EBFArrayBoxFactory const * get_v_const_factory() const noexcept
Return the native AMReX y-face EB factory.
Definition: ERF_EB.H:146
@ theta
Definition: ERF_Kessler.H:26

Referenced by ComputeTurbulentViscosity().

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

◆ ComputeTurbulentViscosityRANS()

void ComputeTurbulentViscosityRANS ( int  level,
Vector< std::unique_ptr< MultiFab >> &  Tau_lev,
const MultiFab &  cons_in,
const MultiFab &  wdist,
MultiFab &  eddyViscosity,
MultiFab &  Diss,
const Geometry &  geom,
bool  use_terrain_fitted_coords,
const std::unique_ptr< MultiFab > &  z_phys_nd,
const TurbChoice &  turbChoice,
const Real  const_grav,
std::unique_ptr< SurfaceLayer > &  SurfLayer,
const MultiFab *  z_0,
const CanopyTurbulence &  canopy,
const MultiFab *  forest_drag,
const MultiFab *  ib_blank,
bool  flat_wall_diss,
Real  ib_small_volfrac,
Real  ib_z0 
)

Function for computing the eddy viscosity with RANS.

Parameters
[in]Tau_levstrain at this level (the realizability cap reads S_ij)
[in]cons_incell center conserved quantities
[in]wdistwall distance
[out]eddyViscosityturbulent viscosity
[out]Dissdissipation of turbulent kinetic energy
[in]geomproblem geometry
[in]use_terrain_fitted_coordsflag for terrain-fitted coordinates
[in]z_phys_ndnodal physical z coordinates
[in]turbChoicecontainer with turbulence parameters
[in]const_gravgravitational acceleration
[in]SurfLayeroptional surface-layer model
[in]z_0roughness length
[in]canopycanopy terms of the k-eqn model (erf.forest_canopy_*)
[in]forest_dragforest drag field C_d LAD, or null without a forest
[in]ib_blanksolid fraction for the fraction-stress immersed wall law, or null without it
[in]flat_wall_disspartial wall cells dissipate with flat ground's first-cell length (erf.if_flat_wall_dissipation)
[in]ib_small_volfracsolid fractions below this count as fluid (eb2.small_volfrac)
[in]ib_z0roughness length of the immersed wall law (erf.if_z0), used without a surface layer
650 {
651  const GpuArray<Real, AMREX_SPACEDIM> cellSizeInv = geom.InvCellSizeArray();
652  const bool use_SurfLayer = (SurfLayer != nullptr);
653 
654  // Optional cap of the geometric length from the diagnosed PBL height
655  const bool lscale_from_pblh = turbChoice.rans_lscale_from_pblh;
656  if (lscale_from_pblh) {
657  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(use_SurfLayer && SurfLayer->computes_pblh(),
658  "erf.rans_lscale_from_pblh needs zlo.type = surface_layer and erf.most.pblh_calc = MYNN25");
659  }
660  const MultiFab* pblh_mf = (lscale_from_pblh) ? SurfLayer->get_pblh(level) : nullptr;
661 
662  Real inv_Pr_t = turbChoice.Pr_t_inv;
663  Real inv_Sc_t = turbChoice.Sc_t_inv;
664  Real inv_sigma_k = one / turbChoice.sigma_k;
665 
666  // One-Equation k model (Axell & Liungman 2001, Environ Fluid Mech)
667  //***********************************************************************************
668  if (turbChoice.rans_type == RANSType::kEqn)
669  {
670  const Real Cmu0 = turbChoice.Cmu0;
671  const Real Cmu0_pow3 = Cmu0 * Cmu0 * Cmu0;
672  const Real inv_Cb_sq = one / (turbChoice.Cb * turbChoice.Cb);
673  const Real Rt_crit = turbChoice.Rt_crit;
674  const Real Rt_min = turbChoice.Rt_min;
675  const Real l_g_max = turbChoice.l_g_max;
676  const Real l_min = turbChoice.rans_lscale_min;
677  const Real abs_g = const_grav;
678  // floor on k: erf.tke_floor if set, otherwise machine epsilon
679  const Real tke_floor = amrex::max(turbChoice.tke_floor, std::numeric_limits<Real>::epsilon());
680 
681  // Canopy cap of the turbulent length scale, L <= alpha / (C_d LAD)
682  const bool use_canopy_length = canopy.enabled && (canopy.length_alpha > zero);
683  AMREX_ALWAYS_ASSERT_WITH_MESSAGE(!use_canopy_length || forest_drag,
684  "erf.forest_canopy_tke needs the forest drag field");
685  const Real canopy_alpha = canopy.length_alpha;
686  // Realizability cap nu_t <= C_r k / S (AL01::realizable_nut); off at 0
687  const Real Cr = turbChoice.rans_realizability;
688  const bool use_cap = (Cr > zero);
689 
690  // Clamped divisor: the select is if-converted, so 1/theta_ref runs even
691  // when theta_ref = 0 and would trip fpe_trap_zero (see ERF_SetupDiff.H)
692  const bool use_ref_theta = (turbChoice.theta_ref > 0);
693  const Real inv_theta_ref = one / amrex::max(turbChoice.theta_ref, std::numeric_limits<Real>::min());
694  const Real inv_theta0 = (use_ref_theta) ? inv_theta_ref : one;
695 
696 #ifdef _OPENMP
697 #pragma omp parallel if (Gpu::notInLaunchRegion())
698 #endif
699  for ( MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi)
700  {
701  Box bxcc = mfi.tilebox();
702 
703  const Array4<Real const>& d_arr = wdist.const_array(mfi);
704  const Array4<Real const>& z0_arr = (use_SurfLayer) ? z_0->const_array(mfi) : Array4<Real const>{};
705  const Array4<Real const>& pblh_arr = (pblh_mf) ? pblh_mf->const_array(mfi) : Array4<Real const>{};
706  const Array4<Real const>& f_drag_arr = (use_canopy_length) ? forest_drag->const_array(mfi)
707  : Array4<Real const>{};
708 
709  // NOTE: the closures deliberately store no subgrid heat flux. hfx_z (Hfx3)
710  // is z-nodal, (0,0,1), while the closure's -K dtheta/dz is cell-centred,
711  // and the theta diffusion of every RK stage overwrites all z-faces (the
712  // surface layer the bottom face) before anything reads them, so the value
713  // written here was both misplaced and unused. The TKE buoyancy source
714  // reads the face fluxes of that diffusion (ERF_TurbKESources.H).
715  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
716  const Array4<Real>& diss = Diss.array(mfi);
717 
718  const Array4<Real const>& cell_data = cons_in.array(mfi);
719 
720  const Array4<Real const>& z_nd_arr = z_phys_nd->const_array(mfi);
721  // fraction-stress immersed wall law: solid fraction, to find the wall cell
722  const Array4<Real const>& ib_arr = (ib_blank) ? ib_blank->const_array(mfi) : Array4<Real const>{};
723  const int ib_klo = geom.Domain().smallEnd(2);
724  const int ib_khi = geom.Domain().bigEnd(2);
725  const Real ib_small = ib_small_volfrac;
726 
727  // Strain components for the cap only; the arrays stay empty otherwise. The k-eqn
728  // closure cannot run with EB terrain (SolverChoice stops the run), so the plain
729  // ComputeSmnSmn is the right form here.
730  const StrainArrays tau = (use_cap) ? strain_arrays(Tau_lev, mfi) : StrainArrays{};
731 
732  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
733  {
735  Real tke = amrex::max(cell_data(i,j,k,RhoKE_comp)/cell_data(i,j,k,Rho_comp), tke_floor);
736 
737  // Estimate stratification
738  Real dzInv = cellSizeInv[2];
739  if (use_terrain_fitted_coords) {
740  // the terrain grid is only deformed in z for now
741  dzInv /= Compute_h_zeta_AtCellCenter(i,j,k, cellSizeInv, z_nd_arr);
742  }
743  // In the wall cell of the fraction-stress immersed wall law the cell below is
744  // solid: read the wall cell itself there, as the first-order extrapolated ghost
745  // of the zlo face does on flat ground, not the solid's temperature
746  int km1 = k-1;
747  if (ib_arr) {
748  const Real b_m = if_rans_beta(ib_arr, i, j, k-1, ib_klo, ib_khi, ib_small);
749  const Real b_0 = if_rans_beta(ib_arr, i, j, k, ib_klo, ib_khi, ib_small);
750  const Real b_p = if_rans_beta(ib_arr, i, j, k+1, ib_klo, ib_khi, ib_small);
751  if (ib_wall::is_wall_cell(b_m, b_0, b_p, ib_small)) { km1 = k; }
752  }
753  Real dtheta_dz = myhalf * ( cell_data(i,j,k+1,RhoTheta_comp)/cell_data(i,j,k+1,Rho_comp)
754  - cell_data(i,j,km1,RhoTheta_comp)/cell_data(i,j,km1,Rho_comp) )*dzInv;
755  Real N2 = abs_g * inv_theta0 * dtheta_dz; // Brunt–Väisälä frequency squared
756  if (!use_ref_theta) {
757  // inv_theta0 == 1, divide by actual theta
758  N2 *= cell_data(i,j,k,Rho_comp) /
759  cell_data(i,j,k,RhoTheta_comp);
760  }
761 
762  // Geometric length scale (AL01, Eqn. 22)
763  // the surface layer's roughness, or the immersed wall law's (fraction_stress) when
764  // there is no surface layer at the bottom
765  Real l_g = (z0_arr) ? KAPPA * (d_arr(i, j, k) + z0_arr(i, j, 0))
766  : (ib_arr) ? KAPPA * (d_arr(i, j, k) + ib_z0)
767  : KAPPA * d_arr(i, j, k);
768 
769  // Enforce a maximum value: fixed, or kappa * 0.1 * zi clamped to
770  // [rans_lscale_min, max_geom_lscale] when the PBL height is diagnosed
771  Real l_cap = l_g_max;
772  if (pblh_arr) {
773  l_cap = amrex::min(l_g_max, amrex::max(l_min, KAPPA * Real(0.1) * pblh_arr(i,j,0)));
774  }
775  l_g = AL01::geom_length(l_g, l_cap);
776 
777  // Turbulent length scale (neutral / stable Eq. 26 / unstable Eq. 28,
778  // the latter bounded through the smoothed Rt)
779  Real length = AL01::turb_length(l_g, N2, tke, Cmu0_pow3, inv_Cb_sq, Rt_crit, Rt_min, eps);
780  // In the canopy the eddies are limited by the foliage: cap L before the
781  // dissipation, Rt and the stability functions are evaluated from it
782  if (f_drag_arr) {
783  length = canopy_turbulence::limited_length(length, f_drag_arr(i,j,k), canopy_alpha);
784  }
785  mu_turb(i, j, k, EddyDiff::Turb_lengthscale) = length;
786 
787  // Dissipation rate (AL01, Eqn. 19)
788  diss(i, j, k) = AL01::dissipation(cell_data(i, j, k, Rho_comp), Cmu0_pow3, tke, length);
789 
790  // erf.if_flat_wall_dissipation (kynema-sgf KLAxell.flat_wall_dissipation): a partial
791  // wall cell of the fraction-stress law has its length at the fluid centroid,
792  // (1 - beta) dz / 2 from the surface, and a nearly solid one then dissipates TKE
793  // several times faster than flat ground's first cell, faster than the wall value
794  // restores it. Keep the centroid length for the viscosity, but dissipate with the
795  // length the same formula gives at dz / 2, the distance of flat ground's first cell
796  if (flat_wall_diss && ib_arr) {
797  const Real bk = ib_arr(i,j,k);
798  if (bk >= ib_small && bk < one - ib_small) { // a partial cell as ib_wall::is_wall_cell sees it
799  Real l_flat = (z0_arr) ? KAPPA * (myhalf / cellSizeInv[2] + z0_arr(i, j, 0))
800  : KAPPA * (myhalf / cellSizeInv[2] + ib_z0);
801  l_flat = AL01::geom_length(l_flat, l_cap);
802  const Real len_flat = AL01::turb_length(l_flat, N2, tke, Cmu0_pow3, inv_Cb_sq, Rt_crit, Rt_min, eps);
803  diss(i, j, k) = AL01::dissipation(cell_data(i, j, k, Rho_comp), Cmu0_pow3, tke, len_flat);
804  }
805  }
806 
807  // Turbulent Richardson number (AL01, Eqn. 29 combined with Eqn. 19),
808  // smoothed below Rt_crit (Burchard & Petersen)
809  Real Rt = AL01::smooth_Rt(AL01::richardson(length, N2, tke, Cmu0_pow3), Rt_crit, Rt_min);
810 
811  // Stability functions (AL01, Eqns. 31 and 32), using the smoothed Rt
812  Real cmu = AL01::cmu(Rt, Cmu0);
813  Real cmu_prime = AL01::cmu_prime(Rt, Cmu0);
814  mu_turb(i, j, k, EddyDiff::RANS_Rt) = Rt;
815  mu_turb(i, j, k, EddyDiff::RANS_cmu) = cmu;
816  mu_turb(i, j, k, EddyDiff::RANS_cmu_prime) = cmu_prime;
817 
818  // Calculate eddy diffusivities
819  // K = rho * nu_t = rho * c_mu * tke^(1/2) * length
820  Real nut = cmu * std::sqrt(tke) * length; // eddy viscosity
821 
822  // Realizability cap nu_t <= C_r k / S; cap_ratio = nu_t / (C_r k / S)
823  Real cap_ratio = zero;
824  if (use_cap) {
825  Real S = std::sqrt(two * ComputeSmnSmn(i,j,k,tau.tau11,tau.tau22,tau.tau33,tau.tau12,tau.tau13,tau.tau23));
826  nut = AL01::realizable_nut(nut, tke, S, Cr, cap_ratio);
827  }
828  mu_turb(i, j, k, EddyDiff::RANS_cap_ratio) = cap_ratio;
829 
830  Real nut_prime = cmu_prime / cmu * nut; // eddy diffusivity, capped with nu_t
831  mu_turb(i, j, k, EddyDiff::Mom_h) = cell_data(i, j, k, Rho_comp) * nut;
832  mu_turb(i, j, k, EddyDiff::Mom_v) = mu_turb(i, j, k, EddyDiff::Mom_h);
833  mu_turb(i, j, k, EddyDiff::Theta_v) = cell_data(i, j, k, Rho_comp) * nut_prime;
834  });
835  }
836  }
837 
838  // Fill the remaining eddy-diffusivity components from Mom_h. Ghost cells
839  // are filled afterwards by the FillBoundary and physical-boundary
840  // extrapolation in ComputeTurbulentViscosity, so only the valid cells are
841  // set here.
842  //***********************************************************************************
843  // EddyDiff mapping : Theta_h KE_h Scalar_h Q_h
844  Vector<Real> Factors = {inv_Pr_t, inv_sigma_k, inv_Sc_t, inv_Sc_t}; // alpha = mu/Pr
845  Gpu::AsyncVector<Real> d_Factors; d_Factors.resize(Factors.size());
846  Gpu::copy(Gpu::hostToDevice, Factors.begin(), Factors.end(), d_Factors.begin());
847  Real* fac_ptr = d_Factors.data();
848 
849  const bool use_KE = ( turbChoice.rans_type == RANSType::kEqn );
850  // With erf.rans_consistent_diffusivities every heat, scalar and moisture
851  // diffusivity is rho * cmu' * sqrt(k) * L, i.e. the Theta_v value set by
852  // the closure; without it the horizontal heat and all scalar diffusivities
853  // are the eddy viscosity over Pr_t / Sc_t (the historical behaviour).
854  const bool consistent = use_KE && turbChoice.rans_consistent_diffusivities;
855 
856 #ifdef _OPENMP
857 #pragma omp parallel if (Gpu::notInLaunchRegion())
858 #endif
859  for ( MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi)
860  {
861  Box bxcc = mfi.tilebox();
862 
863  const Array4<Real>& mu_turb = eddyViscosity.array(mfi);
864 
865  for (auto n = 1; n < EddyDiff::Mom_v; ++n) {
866  int offset = EddyDiff::Mom_v;
867  switch (n)
868  {
869  case EddyDiff::KE_h:
870  if (use_KE) {
871  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
872  {
873  int indx = n;
874  int indx_v = indx + offset;
875  mu_turb(i,j,k,indx) = mu_turb(i,j,k,EddyDiff::Mom_h) * fac_ptr[indx-1];
876  mu_turb(i,j,k,indx_v) = mu_turb(i,j,k,indx);
877  });
878  }
879  break;
880  default:
881  ParallelFor(bxcc, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept
882  {
883  int indx = n;
884  int indx_v = indx + offset;
885 
886  if (consistent) {
887  mu_turb(i,j,k,indx) = mu_turb(i,j,k,EddyDiff::Theta_v);
888  if (indx_v != EddyDiff::Theta_v) {
889  mu_turb(i,j,k,indx_v) = mu_turb(i,j,k,EddyDiff::Theta_v);
890  }
891  } else {
892  mu_turb(i,j,k,indx) = mu_turb(i,j,k,EddyDiff::Mom_h) * fac_ptr[indx-1];
893 
894  // NOTE: Theta_v has already been set for the closure
895  if (!(indx_v == EddyDiff::Theta_v && use_KE)) {
896  mu_turb(i,j,k,indx_v) = mu_turb(i,j,k,indx);
897  }
898  }
899  });
900  break;
901  }
902  }
903  }
904 }
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:71
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real cmu_prime(amrex::Real Rt, amrex::Real Cmu0)
Definition: ERF_RANSClosure.H:128
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real turb_length(amrex::Real l_g, amrex::Real N2, amrex::Real tke, amrex::Real Cmu0_pow3, amrex::Real inv_Cb_sq, amrex::Real Rt_crit, amrex::Real Rt_min, amrex::Real eps)
Definition: ERF_RANSClosure.H:70
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real richardson(amrex::Real length, amrex::Real N2, amrex::Real tke, amrex::Real Cmu0_pow3)
Definition: ERF_RANSClosure.H:111
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real realizable_nut(amrex::Real nut, amrex::Real tke, amrex::Real S, amrex::Real Cr, amrex::Real &ratio)
Definition: ERF_RANSClosure.H:179
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real cmu(amrex::Real Rt, amrex::Real Cmu0)
Definition: ERF_RANSClosure.H:120
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real smooth_Rt(amrex::Real Rt, amrex::Real Rt_crit, amrex::Real Rt_min)
Definition: ERF_RANSClosure.H:40
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real geom_length(amrex::Real l_g_raw, amrex::Real l_g_max)
Definition: ERF_RANSClosure.H:25
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real dissipation(amrex::Real rho, amrex::Real Cmu0_pow3, amrex::Real tke, amrex::Real length)
Definition: ERF_RANSClosure.H:102
@ RANS_Rt
Definition: ERF_IndexDefines.H:306
@ RANS_cap_ratio
Definition: ERF_IndexDefines.H:311
@ RANS_cmu
Definition: ERF_IndexDefines.H:307
@ RANS_cmu_prime
Definition: ERF_IndexDefines.H:308
constexpr int S
Definition: ERF_TwoStreamColumn.H:604
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real limited_length(amrex::Real length, amrex::Real f_drag, amrex::Real alpha)
Definition: ERF_CanopyTurbulence.H:54
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE bool is_wall_cell(amrex::Real b_km1, amrex::Real b_k, amrex::Real b_kp1, amrex::Real small) noexcept
Definition: ERF_ImmersedWallCell.H:57
amrex::Real length_alpha
Definition: ERF_CanopyTurbStruct.H:25
bool enabled
erf.forest_canopy_tke: add the canopy terms to the k-eqn RANS model
Definition: ERF_CanopyTurbStruct.H:18
bool rans_consistent_diffusivities
Definition: ERF_TurbStruct.H:893
amrex::Real l_g_max
Maximum geometric length scale.
Definition: ERF_TurbStruct.H:857
amrex::Real tke_floor
Definition: ERF_TurbStruct.H:884
amrex::Real rans_realizability
Definition: ERF_TurbStruct.H:906
bool rans_lscale_from_pblh
Definition: ERF_TurbStruct.H:898
amrex::Real rans_lscale_min
Definition: ERF_TurbStruct.H:899
amrex::Real Rt_min
Minimum turbulent Reynolds number.
Definition: ERF_TurbStruct.H:856
amrex::Real Cb
One-equation RANS buoyancy coefficient.
Definition: ERF_TurbStruct.H:854
amrex::Real Rt_crit
Critical turbulent Reynolds number.
Definition: ERF_TurbStruct.H:855
amrex::Real Cmu0
One-equation RANS Cmu0 coefficient.
Definition: ERF_TurbStruct.H:853

Referenced by ComputeTurbulentViscosity().

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