223 using namespace amrex;
225 if (alfg <=
Real(0.0)) {
return; }
226 if (feedback <=
Real(0.0)) {
return; }
240 const Box& domain = geom_atm.Domain();
241 int klo = domain.smallEnd(2);
242 int khi = domain.bigEnd(2);
243 const auto&
dx = geom_atm.CellSize();
246 const bool has_latent_global = (Q_lat_atm !=
nullptr) && has_moisture;
249 const bool have_open = (open_frac_atm !=
nullptr) && (roof_h_atm !=
nullptr);
250 const bool use_open = have_open && use_open_weights;
256 tend_dbg.define(cc_source.boxArray(), cc_source.DistributionMap(), 1, 0);
257 tend_dbg.setVal(0.0);
265 cc_source.setVal(0.0,
RhoTheta_comp, 1, cc_source.nGrowVect());
266 if (has_latent_global) {
267 cc_source.setVal(0.0,
RhoQ1_comp, 1, cc_source.nGrowVect());
279 const BoxArray& cba = cc_source.boxArray();
280 bool same_layout = open_frac_atm->DistributionMap() == cc_source.DistributionMap() &&
281 roof_h_atm->DistributionMap() == cc_source.DistributionMap() &&
282 open_frac_atm->boxArray().size() == cba.size() &&
283 roof_h_atm->boxArray().size() == cba.size();
284 for (
int ib = 0; same_layout && ib < cba.size(); ++ib) {
285 const Box slab = makeSlab(cba[ib], 2,
klo);
286 same_layout = (open_frac_atm->boxArray()[ib] == slab) && (roof_h_atm->boxArray()[ib] == slab);
289 "[FIRE] the engine's heat_open_fraction_atm and roof_height_atm must hold one k = 0 slab "
290 "per box of the fire grid's level, in its box order and on its DistributionMapping");
294 norm_mf.define(open_frac_atm->boxArray(), cc_source.DistributionMap(), 1, 0);
295 for (MFIter mfi(norm_mf); mfi.isValid(); ++mfi) {
296 auto const& norm_a = norm_mf.array(mfi);
297 auto const& open_arr = open_frac_atm->const_array(mfi);
298 auto const& roof_arr = roof_h_atm->const_array(mfi);
299 auto const& z_cc_a = z_phys_cc.const_array(mfi);
300 const bool has_nd = (z_phys_nd !=
nullptr);
301 Array4<Real const> z_nd_a;
302 if (has_nd) { z_nd_a = z_phys_nd->const_array(mfi); }
303 amrex::ParallelFor(mfi.validbox(), [=] AMREX_GPU_DEVICE (
int i,
int j,
int ) noexcept {
304 const Real f_open = open_arr(i, j, 0);
305 const Real h_roof = roof_arr(i, j, 0);
306 const Real z_sfc = z_cc_a(i, j,
klo);
309 for (
int kk =
klo; kk <=
khi; ++kk) {
310 const Real zz = z_cc_a(i, j, kk) - z_sfc;
311 const Real zzp1 = (kk <
khi) ? (z_cc_a(i, j, kk + 1) - z_sfc) : (zz + dz_avg);
313 if (h_roof >
Real(0.0)) {
314 const Real zlo2 =
fire_cell_face(z_nd_a, z_cc_a, has_nd, dz_avg, i, j, kk, 0) - ground;
315 const Real zhi2 =
fire_cell_face(z_nd_a, z_cc_a, has_nd, dz_avg, i, j, kk, 1) - ground;
317 if (zhi2 <= h_roof) { s = f_open; }
318 else if (zlo2 < h_roof) { s = f_open + (
Real(1.0) - f_open) * (zhi2 - h_roof) / dzz; }
320 norm += (std::exp(-zz / alfg) - std::exp(-zzp1 / alfg)) * s;
322 norm_a(i, j,
klo) = norm;
327 for (MFIter mfi(cc_source, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
328 auto const& cc_src_a = cc_source.array(mfi);
329 auto const& q_atm_a = Q_atm.array(mfi);
330 auto const& z_cc_a = z_phys_cc.array(mfi);
331 auto const& s_old_a = S_old.array(mfi);
334 const bool has_nd = (z_phys_nd !=
nullptr);
335 Array4<Real const> z_nd_a;
336 if (has_nd) { z_nd_a = z_phys_nd->const_array(mfi); }
338 const Box& bx = mfi.tilebox();
340 const bool has_latent = has_latent_global;
341 Array4<Real const> q_lat_arr;
343 q_lat_arr = Q_lat_atm->const_array(mfi);
345 Array4<Real const> open_arr, roof_arr;
347 open_arr = open_frac_atm->const_array(mfi);
348 roof_arr = roof_h_atm->const_array(mfi);
350 Array4<Real> dbg_arr;
351 if (fire_debug) { dbg_arr = tend_dbg.array(mfi); }
355 Array4<Real const> norm_arr;
356 if (use_open) { norm_arr = norm_mf.const_array(mfi); }
363 Real q_sfc_sensible = feedback * q_atm_a(i, j, 0);
366 q_sfc_latent = feedback * q_lat_arr(i, j, 0);
369 Real z_sfc = z_cc_a(i, j,
klo);
370 Real z_k = z_cc_a(i, j, k) - z_sfc;
371 Real exp_k = std::exp(-z_k / alfg);
373 Real z_kp1 = (k <
khi) ? (z_cc_a(i, j, k + 1) - z_sfc) : (z_k + dz_avg);
374 Real exp_kp1 = std::exp(-z_kp1 / alfg);
395 const Real mass_fac = density_factor ?
rho :
Real(1.0);
396 Real heat_fac = mass_fac;
403 heat_fac /= amrex::max(exner,
Real(1.0e-6));
413 Real w_k = exp_k - exp_kp1;
416 if (bottom_cell) { w_k = (k ==
klo) ?
Real(1.0) :
Real(0.0); }
418 const Real f_open = open_arr(i, j, 0);
419 const Real h_roof = roof_arr(i, j, 0);
424 if (h_roof >
Real(0.0)) {
426 const Real zlo_g = z_lo - ground;
427 const Real zhi_g = z_hi - ground;
428 if (zhi_g <= h_roof) { share = f_open; }
429 else if (zlo_g < h_roof) { share = f_open + (
Real(1.0) - f_open) * (zhi_g - h_roof) /
dz; }
433 const Real norm = norm_arr(i, j,
klo);
436 const Real z_top = (z_cc_a(i, j,
khi) - z_sfc) + dz_avg;
437 const Real legacy =
Real(1.0) - std::exp(-z_top / alfg);
438 w_k = (norm >
Real(1.0e-30)) ? w_k * share * legacy / amrex::max(norm,
Real(1.0e-30)) :
Real(0.0);
443 Real theta_tend, q_tend =
Real(0.0);
444 if (use_open || bottom_cell) {
445 theta_tend = heat_fac * (q_sfc_sensible / Cp_fire) * w_k /
dz;
446 if (has_latent) { q_tend = mass_fac * (q_sfc_latent / L_V) * w_k /
dz; }
448 Real hfx_k = (q_sfc_sensible / Cp_fire) * exp_k;
449 Real hfx_kp1 = (q_sfc_sensible / Cp_fire) * exp_kp1;
450 theta_tend = -heat_fac * (hfx_kp1 - hfx_k) /
dz;
452 Real qfx_k = (q_sfc_latent / L_V) * exp_k;
453 Real qfx_kp1 = (q_sfc_latent / L_V) * exp_kp1;
454 q_tend = -mass_fac * (qfx_kp1 - qfx_k) /
dz;
472 if (dbg_arr) { dbg_arr(i, j, k) = theta_tend * (z_hi - z_lo); }
481 Real Q_max = Q_atm.max(0);
484 Real expected_max = feedback * Q_max / Cp_fire
485 * (
Real(1.0) - std::exp(-dz_avg / alfg)) / dz_avg;
486 amrex::Print() <<
"[FIRE COUPLING] Q_atm_max=" << Q_max
487 <<
" W/m2 alfg=" << alfg <<
" m\n"
488 <<
"[FIRE COUPLING] RhoTheta tendency"
489 <<
" sum=" << rt_tend_sum
490 <<
" max=" << rt_tend_max
491 <<
" expected_surface_max=" << expected_max
492 <<
" [kg K m^-3 s^-1]\n";
503 const Real e_out = Cp_fire * tend_dbg.sum(0) * area;
504 const Real e_in = feedback * Q_atm.sum(0) * area;
515 Real below_roof_max = -
Real(1.0), theta_blocked_max = -
Real(1.0);
517 MultiFab col(open_frac_atm->boxArray(), open_frac_atm->DistributionMap(), 1, 0);
518 MultiFab thb(tend_dbg.boxArray(), tend_dbg.DistributionMap(), 1, 0);
519 col.setVal(0.0); thb.setVal(0.0);
520 for (MFIter mfi(col, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
521 const Box& bx2 = mfi.tilebox();
522 auto const& c = col.array(mfi);
523 auto const&
t = tend_dbg.const_array(mfi);
524 auto const&
z = z_phys_cc.const_array(mfi);
525 auto const& ofrac = open_frac_atm->const_array(mfi);
526 auto const& hr = roof_h_atm->const_array(mfi);
527 const bool has_nd = (z_phys_nd !=
nullptr);
528 Array4<Real const> znd;
529 if (has_nd) { znd = z_phys_nd->const_array(mfi); }
531 const Real f = ofrac(i, j, 0);
532 if (f <=
Real(1.0e-6) || f >=
Real(1.0) -
Real(1.0e-6)) {
return; }
535 for (
int kk =
klo; kk <=
khi; ++kk) {
536 const Real zk =
z(i, j, kk) - ground;
538 if (zk < hr(i, j, 0)) { below +=
t(i, j, kk); }
543 for (MFIter mfi(thb, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
544 const Box& bx = mfi.tilebox();
545 auto const& b = thb.array(mfi);
546 auto const&
z = z_phys_cc.const_array(mfi);
547 auto const& so = S_old.const_array(mfi);
548 auto const& ofrac = open_frac_atm->const_array(mfi);
549 auto const& hr = roof_h_atm->const_array(mfi);
550 const bool has_nd = (z_phys_nd !=
nullptr);
551 Array4<Real const> znd;
552 if (has_nd) { znd = z_phys_nd->const_array(mfi); }
555 if (ofrac(i, j, 0) <
Real(1.0) -
Real(1.0e-6) && zk < hr(i, j, 0)) {
560 below_roof_max = col.max(0);
561 theta_blocked_max = thb.max(0);
568 amrex::Print() <<
"[FIRE COUPLING] energy_in=" << e_in <<
" W energy_out=" << e_out
569 <<
" W ratio=" << ((e_in > 0.0) ? e_out / amrex::max(e_in, std::numeric_limits<Real>::min()) : 0.0)
570 <<
" below_roof_share_max=" << below_roof_max
571 <<
" theta_blocked_max=" << theta_blocked_max <<
"\n";
constexpr amrex::Real Cp_d
Definition: ERF_Constants.H:51
constexpr amrex::Real p_0
Definition: ERF_Constants.H:69
constexpr amrex::Real R_d
Definition: ERF_Constants.H:49
constexpr amrex::Real L_v
Definition: ERF_Constants.H:67
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real getPgivenRTh(const amrex::Real rhotheta, const amrex::Real qv=amrex::Real(0))
Definition: ERF_EOS.H:81
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real fire_cell_thickness(amrex::Real z_lo, amrex::Real z_hi, amrex::Real dz_avg) noexcept
Definition: ERF_FireAtmCoupling.H:177
AMREX_GPU_HOST_DEVICE AMREX_FORCE_INLINE amrex::Real fire_cell_face(amrex::Array4< amrex::Real const > const &z_nd, amrex::Array4< amrex::Real const > const &z_cc, bool has_nd, amrex::Real dz_avg, int i, int j, int k, int upper) noexcept
Definition: ERF_FireAtmCoupling.H:158
#define Rho_comp
Definition: ERF_IndexDefines.H:53
#define RhoTheta_comp
Definition: ERF_IndexDefines.H:54
#define RhoQ1_comp
Definition: ERF_IndexDefines.H:66
const Real rdOcp
Definition: ERF_InitCustomPert_ABL.H:72
const Real dx
Definition: ERF_InitCustomPert_ABL.H:44
AMREX_ALWAYS_ASSERT_WITH_MESSAGE(axis >=0 &&axis< AMREX_SPACEDIM, "prob.axis must be 0, 1 or 2")
const int khi
Definition: ERF_InitCustomPert_Bubble.H:25
const int klo
Definition: ERF_InitCustomPert_KE.H:9
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
@ rho
Definition: ERF_Kessler.H:25
@ qv
Definition: ERF_Kessler.H:31
@ total
Definition: ERF_NOAHMP_Fields.H:137
@ dz
Definition: ERF_AdvanceWDM6.cpp:273
@ t
Definition: ERF_WSM6.H:293
@ p
Definition: ERF_WSM6.H:301
Definition: ERF_ConsoleIO.cpp:17