ERF
Energy Research and Forecasting: An Atmospheric Modeling Code
ERF_FireAtmCoupling.H File Reference
#include <AMReX_MultiFab.H>
#include <AMReX_Geometry.H>
#include <AMReX_Box.H>
#include <AMReX_MFIter.H>
#include <AMReX_MultiFabUtil.H>
#include <cmath>
#include <limits>
#include <ERF_Constants.H>
#include <ERF_IndexDefines.H>
#include <ERF_EOS.H>
#include <ERF_FireGrid.H>
Include dependency graph for ERF_FireAtmCoupling.H:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Functions

void coarsen_fire_flux_to_atm (amrex::MultiFab &Q_atm_out, const amrex::MultiFab &Q_fire, const amrex::Geometry &geom_atm, const FireGrid &fg)
 
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
 
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
 
void apply_fire_tendency_to_cc_source (amrex::MultiFab &cc_source, const amrex::MultiFab &Q_atm, const amrex::MultiFab *Q_lat_atm, const amrex::MultiFab &z_phys_cc, const amrex::MultiFab *z_phys_nd, const amrex::MultiFab &S_old, const amrex::Geometry &geom_atm, amrex::Real alfg, amrex::Real feedback, bool has_moisture, bool fire_debug=false, bool additive=false, const amrex::MultiFab *open_frac_atm=nullptr, const amrex::MultiFab *roof_h_atm=nullptr, bool use_open_weights=false, bool density_factor=false, bool exner_factor=false, bool bottom_cell=false)
 

Variables

constexpr amrex::Real FIRE_ATM_ALFG_DEFAULT = amrex::Real(45.0)
 

Function Documentation

◆ apply_fire_tendency_to_cc_source()

void apply_fire_tendency_to_cc_source ( amrex::MultiFab &  cc_source,
const amrex::MultiFab &  Q_atm,
const amrex::MultiFab *  Q_lat_atm,
const amrex::MultiFab &  z_phys_cc,
const amrex::MultiFab *  z_phys_nd,
const amrex::MultiFab &  S_old,
const amrex::Geometry &  geom_atm,
amrex::Real  alfg,
amrex::Real  feedback,
bool  has_moisture,
bool  fire_debug = false,
bool  additive = false,
const amrex::MultiFab *  open_frac_atm = nullptr,
const amrex::MultiFab *  roof_h_atm = nullptr,
bool  use_open_weights = false,
bool  density_factor = false,
bool  exner_factor = false,
bool  bottom_cell = false 
)
inline
222 {
223  using namespace amrex;
224 
225  if (alfg <= Real(0.0)) { return; }
226  if (feedback <= Real(0.0)) { return; }
227 
228  // Source conversion must use the same dry-air heat capacity as ERF's EOS.
229  constexpr Real Cp_fire = Cp_d;
230  // and the latent heat ERF uses (an engine converting a vapour mass flux to
231  // its latent flux multiplies by this same L_v, so the water round-trips exactly)
232  constexpr Real L_V = L_v;
233  // Exner function of the cell from its rho theta (erf.fire.heat_tendency_exner):
234  // heating q [W/m3] gives d(theta)/dt = q / (rho cp Pi), so the rho theta
235  // tendency carries 1/Pi; without it the enthalpy injected is Pi times the
236  // flux, 4.5 % short at 850 hPa. WRF-SFIRE omits the factor; it is on by
237  // default here.
238  const Real rdOcp = R_d / Cp_d;
239 
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();
244  Real dz_avg = dx[2];
245 
246  const bool has_latent_global = (Q_lat_atm != nullptr) && has_moisture;
247  // The open fraction and roof height are handed in whenever the engine
248  // reports them; the weights only use them when asked.
249  const bool have_open = (open_frac_atm != nullptr) && (roof_h_atm != nullptr);
250  const bool use_open = have_open && use_open_weights;
251 
252  // Debug only: the fire tendency alone, times the cell thickness (in additive mode
253  // cc_source also holds the other sources), for the energy check below.
254  MultiFab tend_dbg;
255  if (fire_debug) {
256  tend_dbg.define(cc_source.boxArray(), cc_source.DistributionMap(), 1, 0);
257  tend_dbg.setVal(0.0);
258  }
259 
260  // Legacy (overwrite) mode: zero the components we own before writing them,
261  // so any state left over from a previous stage does not leak through. In
262  // additive mode make_sources has just rebuilt the source for this stage,
263  // so the fire tendency is accumulated once per stage on top of it.
264  if (!additive) {
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());
268  }
269  }
270 
271  // Column normalisation of the open-fraction weights: one k = klo slab per
272  // box, filled once per column (the boxes hold whole columns, which the
273  // fire prerequisites assert), before the tiled loop that reads it.
274  // The engine's open fraction and roof height are read with the level's
275  // MFIter below, so they must hold one k = klo slab per box of the level,
276  // in its order and on its DistributionMapping (only a debug build checks
277  // that inside AMReX).
278  if (have_open) {
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);
287  }
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");
291  }
292  MultiFab norm_mf;
293  if (use_open) {
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 /*kslab*/) noexcept {
304  const Real f_open = open_arr(i, j, 0);
305  const Real h_roof = roof_arr(i, j, 0); // above the ground
306  const Real z_sfc = z_cc_a(i, j, klo);
307  const Real ground = fire_cell_face(z_nd_a, z_cc_a, has_nd, dz_avg, i, j, klo, 0);
308  Real norm = Real(0.0);
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);
312  Real s = Real(1.0);
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;
316  const Real dzz = fire_cell_thickness(zlo2, zhi2, dz_avg);
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; }
319  }
320  norm += (std::exp(-zz / alfg) - std::exp(-zzp1 / alfg)) * s;
321  }
322  norm_a(i, j, klo) = norm;
323  });
324  }
325  }
326 
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);
332  // Faces of cell k: the mean of its four lower (upper) nodes where the
333  // nodal heights exist, half a cell below (above) the centre otherwise
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); }
337 
338  const Box& bx = mfi.tilebox();
339 
340  const bool has_latent = has_latent_global;
341  Array4<Real const> q_lat_arr;
342  if (has_latent) {
343  q_lat_arr = Q_lat_atm->const_array(mfi);
344  }
345  Array4<Real const> open_arr, roof_arr;
346  if (use_open) {
347  open_arr = open_frac_atm->const_array(mfi);
348  roof_arr = roof_h_atm->const_array(mfi);
349  }
350  Array4<Real> dbg_arr;
351  if (fire_debug) { dbg_arr = tend_dbg.array(mfi); }
352 
353  // Column normalisation of the open-fraction weights, computed once per
354  // column above the tile loop.
355  Array4<Real const> norm_arr;
356  if (use_open) { norm_arr = norm_mf.const_array(mfi); }
357 
358  amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE(const IntVect& iv) {
359  int i = iv[0];
360  int j = iv[1];
361  int k = iv[2];
362 
363  Real q_sfc_sensible = feedback * q_atm_a(i, j, 0);
364  Real q_sfc_latent = Real(0.0);
365  if (has_latent) {
366  q_sfc_latent = feedback * q_lat_arr(i, j, 0);
367  }
368 
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);
372 
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);
375 
376  // The cell's own thickness, face to face: the column then receives
377  // sum_k w_k of the flux, 1 - exp(-z_top/alfg), on any vertical
378  // spacing (the centre-to-centre spacing used before put a share
379  // dz_cell / dz_cc into each cell, 2 / (1 + r) of the flux on a grid
380  // stretched by r)
381  const Real z_lo = fire_cell_face(z_nd_a, z_cc_a, has_nd, dz_avg, i, j, k, 0);
382  const Real z_hi = fire_cell_face(z_nd_a, z_cc_a, has_nd, dz_avg, i, j, k, 1);
383  const Real dz = fire_cell_thickness(z_lo, z_hi, dz_avg);
384 
385  Real rho = s_old_a(i, j, k, Rho_comp);
386  if (rho <= Real(0.0)) { rho = Real(1.2); }
387  // Heating gives d(rho theta)/dt = -(1/Cp) dQ/dz, with no density
388  // factor. The legacy form carries an extra rho, so its injected
389  // energy is rho times the flux; density_factor = false drops it.
390  // The Exner factor divides the tendency by Pi(p) so that the
391  // enthalpy, not rho theta, integrates to the flux.
392  // Preserve the explicit legacy density option, but apply Exner
393  // only to heat. Q_lat / L_V is a water mass flux: converting it
394  // to d(rho_d q_v)/dt must not multiply that mass by 1/Pi.
395  const Real mass_fac = density_factor ? rho : Real(1.0);
396  Real heat_fac = mass_fac;
397  if (exner_factor) {
398  const Real rhotheta = amrex::max(s_old_a(i, j, k, RhoTheta_comp), Real(1.0e-10));
399  // the moist pressure where there is vapour (about 1 % in Pi at 15 g/kg)
400  const Real qv = has_moisture ? amrex::max(s_old_a(i, j, k, RhoQ1_comp), Real(0.0)) / rho : Real(0.0);
401  const Real p = getPgivenRTh(rhotheta, qv);
402  const Real exner = std::pow(p / p_0, rdOcp);
403  heat_fac /= amrex::max(exner, Real(1.0e-6));
404  }
405 
406  // Layer weight: the fraction of the column's exponential profile
407  // that falls in this cell. Without an open fraction this is the flux
408  // difference form used since the coupling was written. With the
409  // open-fraction option, cells below the sampled roof height carry
410  // only their open share and the column is renormalised over the
411  // cells that remain, so the injected energy per column is unchanged
412  // and none of it lands inside a building.
413  Real w_k = exp_k - exp_kp1;
414  // erf.fire.heat_profile = bottom_cell: the whole flux enters the
415  // lowest cell of the column (the LSFIRE coupling)
416  if (bottom_cell) { w_k = (k == klo) ? Real(1.0) : Real(0.0); }
417  if (use_open) {
418  const Real f_open = open_arr(i, j, 0);
419  const Real h_roof = roof_arr(i, j, 0);
420  // Open share of this cell: fully open above the roof, f_open
421  // below it, linear across the cell that holds the roof.
422  // The roof height is above the ground, so are the cell's faces.
423  Real share = Real(1.0);
424  if (h_roof > Real(0.0)) {
425  const Real ground = fire_cell_face(z_nd_a, z_cc_a, has_nd, dz_avg, i, j, klo, 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; }
430  }
431  // Column normalisation over the open shares of the same
432  // profile, precomputed per column above.
433  const Real norm = norm_arr(i, j, klo);
434  // The legacy profile integrates to (1 - exp(-z_top/alfg)); keep
435  // that total so the two modes inject the same energy per column.
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);
439  }
440 
441  // Without the open-fraction option the flux-difference expression
442  // is kept (exp_k - exp_kp1 times the flux, over the cell thickness).
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; }
447  } else {
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;
451  if (has_latent) {
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;
455  }
456  }
457  if (additive) {
458  cc_src_a(i, j, k, RhoTheta_comp) += theta_tend;
459  } else {
460  // OVERWRITE (see file-level RK-stage safety note).
461  cc_src_a(i, j, k, RhoTheta_comp) = theta_tend;
462  }
463  if (has_latent) {
464  if (additive) {
465  cc_src_a(i, j, k, RhoQ1_comp) += q_tend;
466  } else {
467  cc_src_a(i, j, k, RhoQ1_comp) = q_tend;
468  }
469  }
470  // the cell's true thickness (not the floored one), so a degenerate
471  // cell's lost energy shows in the ratio
472  if (dbg_arr) { dbg_arr(i, j, k) = theta_tend * (z_hi - z_lo); }
473  });
474  }
475 
476  // Tendency diagnostics: runs on host after GPU kernel completes.
477  // Prints once per apply call (3x per timestep for RK3).
478  // Expected rt_tend_max ~ feedback * Q_max / Cp * (1 - exp(-dz/alfg)) / dz
479  // (times rho with the density factor) on a uniform grid
480  if (fire_debug) {
481  Real Q_max = Q_atm.max(0);
482  Real rt_tend_sum = cc_source.sum(RhoTheta_comp);
483  Real rt_tend_max = cc_source.max(RhoTheta_comp);
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";
493 
494  // Energy check: Cp times the sum over cells of (rho theta tendency) dz
495  // per unit area, against the flux handed in. The profile carries
496  // 1 - exp(-z_top/alfg) of the flux, essentially all of it for any
497  // realistic domain height, so with neither the density nor the Exner
498  // factor the ratio is that (the density factor multiplies it by rho,
499  // the Exner factor by 1/Pi, both profile-weighted means, essentially
500  // their values near the surface). The largest fire tendency inside a fully blocked cell below the roof must be
501  // zero with the open-fraction option.
502  const Real area = dx[0] * dx[1];
503  const Real e_out = Cp_fire * tend_dbg.sum(0) * area;
504  const Real e_in = feedback * Q_atm.sum(0) * area;
505  // Columns that straddle a footprint (0 < open fraction < 1) are where
506  // the placement matters: fully covered columns carry no flux once their
507  // fire cells are masked. For those partial columns report the largest
508  // share of the column's heating that lands below the roof H (above the
509  // ground). With B the plain profile's share below the roof (measured
510  // from the first cell centre) and L = 1 - exp(-z_top/alfg), the
511  // open-fraction weights give f B L / (L - (1 - f) B), more than f B
512  // because the column is renormalised; and the largest potential temperature of the
513  // state in cells below a roof, which shows heat that earlier steps left
514  // inside buildings.
515  Real below_roof_max = -Real(1.0), theta_blocked_max = -Real(1.0);
516  if (have_open) {
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); }
530  amrex::ParallelFor(bx2, [=] AMREX_GPU_DEVICE (int i, int j, int /*k*/) noexcept {
531  const Real f = ofrac(i, j, 0);
532  if (f <= Real(1.0e-6) || f >= Real(1.0) - Real(1.0e-6)) { return; }
533  const Real ground = fire_cell_face(znd, z, has_nd, dz_avg, i, j, klo, 0);
534  Real below = Real(0.0), total = Real(0.0);
535  for (int kk = klo; kk <= khi; ++kk) {
536  const Real zk = z(i, j, kk) - ground;
537  total += t(i, j, kk);
538  if (zk < hr(i, j, 0)) { below += t(i, j, kk); }
539  }
540  c(i, j, 0) = (total > Real(1.0e-30)) ? below / amrex::max(total, Real(1.0e-30)) : Real(0.0);
541  });
542  }
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); }
553  amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) noexcept {
554  const Real zk = z(i, j, k) - fire_cell_face(znd, z, has_nd, dz_avg, i, j, klo, 0);
555  if (ofrac(i, j, 0) < Real(1.0) - Real(1.0e-6) && zk < hr(i, j, 0)) {
556  b(i, j, k) = so(i, j, k, RhoTheta_comp) / so(i, j, k, Rho_comp);
557  }
558  });
559  }
560  below_roof_max = col.max(0);
561  theta_blocked_max = thb.max(0);
562  }
563  // The ratio divides only when e_in > 0, but the optimiser evaluates the division
564  // speculatively for both arms of the selection, and 0/0 on the first lagged step
565  // raised the invalid flag that aborts the run under amrex.fpe_trap_invalid; the
566  // floored denominator (the smallest normal Real, so it holds in single
567  // precision too) keeps the discarded value finite and the printed one unchanged.
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";
572  }
573 }
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

Referenced by FireCoupler::add_sources().

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

◆ coarsen_fire_flux_to_atm()

void coarsen_fire_flux_to_atm ( amrex::MultiFab &  Q_atm_out,
const amrex::MultiFab &  Q_fire,
const amrex::Geometry &  geom_atm,
const FireGrid &  fg 
)
inline
117 {
118  const int C = fg.C;
119  if (fg.atm_lo == amrex::IntVect::TheZeroVector()) {
120  amrex::average_down(Q_fire, Q_atm_out, fg.geom, geom_atm, 0, Q_fire.nComp(),
121  amrex::IntVect(C, C, 1));
122  return;
123  }
124 
126  Q_fire.boxArray().size() == Q_atm_out.boxArray().size() &&
127  Q_fire.DistributionMap() == Q_atm_out.DistributionMap(),
128  "[FIRE] coarsen_fire_flux_to_atm: the fire grid and the atmospheric slabs must pair box for box");
129  AMREX_ALWAYS_ASSERT(Q_atm_out.nComp() >= Q_fire.nComp());
130 
131  const int lo_x = fg.atm_lo[0];
132  const int lo_y = fg.atm_lo[1];
133  const int ncomp = Q_fire.nComp();
134  const amrex::Real volfrac = amrex::Real(1.0) / amrex::Real(C * C);
135  for (amrex::MFIter mfi(Q_atm_out); mfi.isValid(); ++mfi) {
136  const amrex::Box& bx = mfi.validbox();
137  auto crse = Q_atm_out.array(mfi);
138  auto fine = Q_fire.const_array(mfi);
139  amrex::ParallelFor(bx, ncomp, [=] AMREX_GPU_DEVICE (int i, int j, int k, int n) noexcept
140  {
141  const int ii = (i - lo_x) * C;
142  const int jj = (j - lo_y) * C;
143  amrex::Real c = amrex::Real(0.0);
144  for (int q = 0; q < C; ++q) {
145  for (int r = 0; r < C; ++r) {
146  c += fine(ii + r, jj + q, 0, n);
147  }
148  }
149  crse(i, j, k, n) = volfrac * c;
150  });
151  }
152 }
AMREX_ALWAYS_ASSERT(bx.length()[2]==khi+1)
@ fg
Definition: ERF_AdvanceMorrison.cpp:111
@ q
Definition: ERF_WSM6.H:294

Referenced by FireCoupler::fill_plot_var(), and FireCoupler::update_flux_buffer().

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

◆ fire_cell_face()

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
162 {
163  if (has_nd) {
164  const int kf = k + upper;
165  return amrex::Real(0.25) * (z_nd(i, j, kf) + z_nd(i+1, j, kf)
166  + z_nd(i, j+1, kf) + z_nd(i+1, j+1, kf));
167  }
168  return z_cc(i, j, k) + (upper ? amrex::Real(0.5) : amrex::Real(-0.5)) * dz_avg;
169 }

Referenced by apply_fire_tendency_to_cc_source().

Here is the caller graph for this function:

◆ fire_cell_thickness()

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
178 {
179  return amrex::max(z_hi - z_lo, amrex::Real(1.0e-6) * dz_avg);
180 }

Referenced by apply_fire_tendency_to_cc_source().

Here is the caller graph for this function:

Variable Documentation

◆ FIRE_ATM_ALFG_DEFAULT

constexpr amrex::Real FIRE_ATM_ALFG_DEFAULT = amrex::Real(45.0)
constexpr