Function to compute turbulent viscosity with PBL.
34 const bool use_terrain = (z_phys_nd !=
nullptr);
58 Real eps = std::numeric_limits<Real>::epsilon();
61 #pragma omp parallel if (Gpu::notInLaunchRegion())
63 for ( MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi) {
65 const Box &bx = mfi.growntilebox(1);
66 const Array4<Real const> &cell_data = cons_in.array(mfi);
67 const Array4<Real > &K_turb = eddyViscosity.array(mfi);
68 const Array4<Real const> &uvel =
xvel.array(mfi);
69 const Array4<Real const> &vvel =
yvel.array(mfi);
74 const Box &dbx = geom.Domain();
75 Box sbx(bx.smallEnd(), bx.bigEnd());
77 AMREX_ALWAYS_ASSERT(sbx.smallEnd(2) == dbx.smallEnd(2) && sbx.bigEnd(2) == dbx.bigEnd(2));
79 const GeometryData gdata = geom.data();
81 const Box xybx = PerpendicularBox<ZDir>(bx, IntVect{0,0,0});
82 FArrayBox qintegral(xybx,2);
83 qintegral.setVal<RunOn::Device>(0.0);
84 FArrayBox qturb(bx,1); FArrayBox qturb_old(bx,1);
85 const Array4<Real> qint = qintegral.array();
86 const Array4<Real> qvel = qturb.array();
87 const Array4<Real> qvel_old = qturb_old.array();
91 const Array4<Real const> &z_nd_arr = z_phys_nd->array(mfi);
92 const auto invCellSize = geom.InvCellSizeArray();
93 ParallelFor(Gpu::KernelInfo().setReduction(
true), bx,
94 [=] AMREX_GPU_DEVICE (
int i,
int j,
int k, Gpu::Handler
const& handler) noexcept
97 qvel_old(i,j,k) = std::sqrt(cell_data(i,j,k,
RhoQKE_comp) / cell_data(i,j,k,
Rho_comp) + eps);
98 AMREX_ASSERT_WITH_MESSAGE(qvel(i,j,k) > 0.0,
"QKE must have a positive value");
99 AMREX_ASSERT_WITH_MESSAGE(qvel_old(i,j,k) > 0.0,
"Old QKE must have a positive value");
101 if (sbx.contains(i,j,k)) {
104 Gpu::deviceReduceSum(&qint(i,j,0,0), Zval*qvel(i,j,k)*dz, handler);
105 Gpu::deviceReduceSum(&qint(i,j,0,1), qvel(i,j,k)*dz, handler);
109 ParallelFor(Gpu::KernelInfo().setReduction(
true), bx,
110 [=] AMREX_GPU_DEVICE (
int i,
int j,
int k, Gpu::Handler
const& handler) noexcept
113 qvel_old(i,j,k) = std::sqrt(cell_data(i,j,k,
RhoQKE_comp) / cell_data(i,j,k,
Rho_comp) + eps);
114 AMREX_ASSERT_WITH_MESSAGE(qvel(i,j,k) > 0.0,
"QKE must have a positive value");
115 AMREX_ASSERT_WITH_MESSAGE(qvel_old(i,j,k) > 0.0,
"Old QKE must have a positive value");
118 if (sbx.contains(i,j,k)) {
119 const Real Zval = gdata.ProbLo(2) + (k + 0.5)*gdata.CellSize(2);
120 Gpu::deviceReduceSum(&qint(i,j,0,0), Zval*qvel(i,j,k), handler);
121 Gpu::deviceReduceSum(&qint(i,j,0,1), qvel(i,j,k), handler);
126 Real dz_inv = geom.InvCellSize(2);
127 const auto& dxInv = geom.InvCellSizeArray();
128 int izmin = geom.Domain().smallEnd(2);
129 int izmax = geom.Domain().bigEnd(2);
132 Real d_kappa =
KAPPA;
135 const auto& t_mean_mf = most->get_mac_avg(level,2);
136 const auto& u_star_mf = most->get_u_star(level);
137 const auto& t_star_mf = most->get_t_star(level);
139 const auto& tm_arr = t_mean_mf->array(mfi);
140 const auto& u_star_arr = u_star_mf->array(mfi);
141 const auto& t_star_arr = t_star_mf->array(mfi);
143 const Array4<Real const> z_nd_arr = use_terrain ? z_phys_nd->array(mfi) : Array4<Real>{};
145 ParallelFor(bx, [=] AMREX_GPU_DEVICE (
int i,
int j,
int k) noexcept
150 Real dthetadz, dudz, dvdz;
152 uvel, vvel, cell_data, izmin, izmax, dz_inv/met_h_zeta,
153 c_ext_dir_on_zlo, c_ext_dir_on_zhi,
154 u_ext_dir_on_zlo, u_ext_dir_on_zhi,
155 v_ext_dir_on_zlo, v_ext_dir_on_zhi,
156 dthetadz, dudz, dvdz);
159 Real surface_heat_flux = -u_star_arr(i,j,0) * t_star_arr(i,j,0);
160 Real theta0 = tm_arr(i,j,0);
162 if (std::abs(surface_heat_flux) > eps) {
163 l_obukhov = ( theta0 * u_star_arr(i,j,0) * u_star_arr(i,j,0) ) /
164 ( d_kappa * d_gravity * t_star_arr(i,j,0) );
166 l_obukhov = std::numeric_limits<Real>::max();
170 AMREX_ASSERT(l_obukhov != 0);
171 int lk = amrex::max(k,0);
172 const Real zval = use_terrain ?
Compute_Zrel_AtCellCenter(i,j,lk,z_nd_arr) : gdata.ProbLo(2) + (lk + 0.5)*gdata.CellSize(2);
173 const Real zeta = zval/l_obukhov;
176 l_S =
KAPPA*zval/3.7;
177 }
else if (zeta >= 0) {
178 l_S =
KAPPA*zval/(1+2.7*zeta);
180 l_S =
KAPPA*zval*std::pow(1.0 - 100.0 * zeta, 0.2);
185 if (qint(i,j,0,1) > 0.0) {
186 l_T = 0.23*qint(i,j,0,0)/qint(i,j,0,1);
188 l_T = std::numeric_limits<Real>::max();
194 Real N_brunt_vaisala = std::sqrt(
CONST_GRAV/theta0 * dthetadz);
196 Real qc =
CONST_GRAV/theta0 * surface_heat_flux * l_T;
197 qc = std::pow(qc,1.0/3.0);
198 l_B = (1.0 + 5.0*std::sqrt(qc/(N_brunt_vaisala * l_T))) * qvel(i,j,k)/N_brunt_vaisala;
200 l_B = qvel(i,j,k) / N_brunt_vaisala;
203 l_B = std::numeric_limits<Real>::max();
207 Real l_comb = 1.0 / (1.0/l_S + 1.0/l_T + 1.0/l_B);
213 Real shearProd = dudz*dudz + dvdz*dvdz;
214 Real buoyProd = -(
CONST_GRAV/theta0) * dthetadz;
217 Real qe2 = B1 * l_comb_old * ( lSM * shearProd + lSH * buoyProd );
218 Real qe = (qe2 < 0.0) ? 0.0 : std::sqrt(qe2);
219 Real one_m_alpha = (qvel(i,j,k) > qe) ? 1.0 : qvel(i,j,k) / (qe + eps);
220 Real one_m_alpha2 = one_m_alpha * one_m_alpha;
223 Real l2_over_q2 = l_comb*l_comb/(qvel(i,j,k)*qvel(i,j,k));
224 Real GM = l2_over_q2 * shearProd;
225 Real GH = l2_over_q2 * buoyProd;
226 Real E1 = 1.0 + one_m_alpha2 * ( 6.0*A1*A1*GM - 9.0*A1*A2*(1.0-C2)*GH );
227 Real E2 = one_m_alpha2 * ( -3.0*A1*(4.0*A1 + 3.0*A2*(1.0-C5))*(1.0-C2)*GH );
228 Real E3 = one_m_alpha2 * ( 6.0*A1*A2*GM );
229 Real E4 = 1.0 + one_m_alpha2 * ( -12.0*A1*A2*(1.0-C2)*GH - 3.0*A2*B2*(1.0-C3)*GH );
230 Real R1 = one_m_alpha * ( A1*(1.0-3.0*C1) );
231 Real R2 = one_m_alpha * A2;
233 Real SM = (R2*E2 - R1*E4)/(E2*E3 - E1*E4);
234 Real SH = (R1*E3 - R2*E1)/(E2*E3 - E1*E4);
248 Error(
"YSU Model not implemented yet");
259 #pragma omp parallel if (Gpu::notInLaunchRegion())
261 for ( MFIter mfi(eddyViscosity,TilingIfNotGPU()); mfi.isValid(); ++mfi) {
264 const Box &bx = mfi.growntilebox(1);
265 const Box &dbx = geom.Domain();
266 Box sbx(bx.smallEnd(), bx.bigEnd());
268 AMREX_ALWAYS_ASSERT(sbx.smallEnd(2) == dbx.smallEnd(2) && sbx.bigEnd(2) == dbx.bigEnd(2));
271 const auto& cell_data = cons_in.const_array(mfi);
273 const auto& uvel =
xvel.const_array(mfi);
274 const auto& vvel =
yvel.const_array(mfi);
276 const auto& z0_arr = most->get_z0(level)->const_array();
277 const auto& ws10av_arr = most->get_mac_avg(level,4)->const_array(mfi);
278 const auto& t10av_arr = most->get_mac_avg(level,2)->const_array(mfi);
281 const auto& t_surf_arr = most->get_t_surf(level)->const_array(mfi);
283 const auto& z_nd_arr = z_phys_nd->array(mfi);
284 const Real most_zref = most->get_zref();
287 if (most_zref != 10.0) {
288 amrex::Abort(
"MOST Zref must be 10m for YSU PBL scheme");
292 const GeometryData gdata = geom.data();
293 const Box xybx = PerpendicularBox<ZDir>(bx, IntVect{0,0,0});
294 FArrayBox pbl_height(xybx,1);
295 const auto& pblh_arr = pbl_height.array();
303 ParallelFor(xybx, [=] AMREX_GPU_DEVICE (
int i,
int j,
int) noexcept
307 const Real t_surf = t_surf_arr(i,j,0);
308 const Real t_layer = t10av_arr(i,j,0);
309 const Real ws_layer = ws10av_arr(i,j,0);
310 const Real Rib_layer =
CONST_GRAV * most_zref / (ws_layer*ws_layer) * (t_layer - t_surf)/(t_layer);
313 if (Rib_layer < unst_Ribcr) {
314 Abort(
"For now, YSU PBL only supports stable conditions");
326 const Real z0 = z0_arr(i,j,0);
327 const Real Rossby = ws_layer/(f0*z0);
328 Rib_cr = min(0.16*std::pow(1.0e-7*Rossby,-0.18),0.3);
331 bool above_critical =
false;
333 Real Rib_up = Rib_layer, Rib_dn;
335 while (!above_critical and bx.contains(i,j,kpbl+1)) {
337 const Real zval = use_terrain ?
Compute_Zrel_AtCellCenter(i,j,kpbl,z_nd_arr) : gdata.ProbLo(2) + (kpbl + 0.5)*gdata.CellSize(2);
338 const Real ws2_level = (uvel(i,j,kpbl)+uvel(i+1,j,kpbl))*(uvel(i,j,kpbl)+uvel(i+1,j,kpbl)) + (vvel(i,j,kpbl)+vvel(i,j+1,kpbl))*(uvel(i,j,kpbl)+uvel(i,j+1,kpbl));
341 Rib_up = (
theta-base_theta)/base_theta *
CONST_GRAV * zval / ws2_level;
342 above_critical = Rib_up >= Rib_cr;
346 if (Rib_dn >= Rib_cr) {
348 }
else if (Rib_up <= Rib_cr)
351 interp_fact = (Rib_cr - Rib_dn) / (Rib_up - Rib_dn);
354 const Real zval_up = use_terrain ?
Compute_Zrel_AtCellCenter(i,j,kpbl,z_nd_arr) : gdata.ProbLo(2) + (kpbl + 0.5)*gdata.CellSize(2);
355 const Real zval_dn = use_terrain ?
Compute_Zrel_AtCellCenter(i,j,kpbl-1,z_nd_arr) : gdata.ProbLo(2) + (kpbl-1 + 0.5)*gdata.CellSize(2);
356 pblh_arr(i,j,0) = zval_dn + interp_fact*(zval_up-zval_dn);
357 if (pblh_arr(i,j,0) < 0.5*(zval_up+zval_dn) ) {
constexpr amrex::Real KAPPA
Definition: ERF_Constants.H:20
constexpr amrex::Real CONST_GRAV
Definition: ERF_Constants.H:21
#define RhoQKE_comp
Definition: IndexDefines.H:15
AMREX_GPU_DEVICE AMREX_FORCE_INLINE void ComputeVerticalDerivativesPBL(int i, int j, int k, const amrex::Array4< const amrex::Real > &uvel, const amrex::Array4< const amrex::Real > &vvel, const amrex::Array4< const amrex::Real > &cell_data, const int izmin, const int izmax, const amrex::Real &dz_inv, const bool c_ext_dir_on_zlo, const bool c_ext_dir_on_zhi, const bool u_ext_dir_on_zlo, const bool u_ext_dir_on_zhi, const bool v_ext_dir_on_zlo, const bool v_ext_dir_on_zhi, amrex::Real &dthetadz, amrex::Real &dudz, amrex::Real &dvdz)
Definition: PBLModels.H:14
AMREX_GPU_DEVICE AMREX_FORCE_INLINE amrex::Real Compute_Zrel_AtCellCenter(const int &i, const int &j, const int &k, const amrex::Array4< const amrex::Real > &z_nd)
Definition: TerrainMetrics.H:344
@ yvel_bc
Definition: IndexDefines.H:56
@ cons_bc
Definition: IndexDefines.H:47
@ xvel_bc
Definition: IndexDefines.H:55
@ ext_dir
Definition: IndexDefines.H:152
@ PBL_lengthscale
Definition: IndexDefines.H:133
@ QKE_v
Definition: IndexDefines.H:130
@ theta
Definition: MM5.H:20
@ rho
Definition: Kessler.H:30
amrex::Real pbl_mynn_A2
Definition: TurbStruct.H:282
amrex::Real pbl_mynn_C2
Definition: TurbStruct.H:286
amrex::Real pbl_mynn_A1
Definition: TurbStruct.H:281
amrex::Real pbl_mynn_C3
Definition: TurbStruct.H:287
amrex::Real pbl_mynn_B2
Definition: TurbStruct.H:284
bool pbl_ysu_over_land
Definition: TurbStruct.H:294
amrex::Real pbl_mynn_C1
Definition: TurbStruct.H:285
amrex::Real pbl_mynn_C5
Definition: TurbStruct.H:289
amrex::Real pbl_ysu_land_Ribcr
Definition: TurbStruct.H:295
amrex::Real pbl_ysu_coriolis_freq
Definition: TurbStruct.H:293
amrex::Real pbl_mynn_B1
Definition: TurbStruct.H:283
amrex::Real pbl_ysu_unst_Ribcr
Definition: TurbStruct.H:296